# GraphQL × Nodejs(TypeScript) ###### tags: `GraphQL` # GraphQLとは - GraphQLはAPIのために作られたクエリ言語 - 既存のデータに対するクエリを実行するランタイム - 理解できる完全な形でAPI内のデータについて記述 - クライアント側から必要な内容だけを問い合わせられる - 漸次的にAPIを進化させることが容易になり、強力な開発者ツールを実現できる - **GraphQLは、クライアントアプリケーションが必要なデータをAPIからフェッチするために設計された言語** - **クライアントアプリケーションはバックエンドAPIから必要なデータの型とシェイプを取得できる** - **GraphQLでは、どんなタイプのリクエストでも、クライアントアプリケーションが呼び出すエンドポイントは1つだけでいい** - **GraphQLはSQLとよく似ていますが、フロントエンドで機能する** > ランタイムとは > - プログラムとかを動かすとき(実行時)のこと。 > - プログラムとかを動かすときに必要な部品のこと > - 「動かすときに必要なプログラムの部品」を意図したランタイムライブラリ/ランタイムモジュール/ランタイムパッケージと呼ぶこともある > - 要約すると実行時のこと、あるいは、実行時に必要な物のこと ## なぜ使うのか? > アプリケーションのパフォーマンスを高めるには、ニュース フィード、メッセージング、Facebook ウォール投稿とそのコメントや「いいね!」、投稿へのコメントの「いいね!」などのモジュールを提供するために必要なデータシェイプだけを照会しなければなりません > ユーザーのidとemailだけ (name は除外)、またはユーザーのnameだけを返すように指定することはできませんでした。しかし、RESTAPIはユーザーデータをすべて返します。このため、ユーザーデータに上記の例よりも多くのパラメーターが含まれている場合には、その分だけ処理が面倒になってしまいます。 > RESTAPIと異なり、GraphQLAPIがエクスポーズするエンドポイントは1つだけです。このエンドポイントが、クライアントからのすべてのリクエストに対応します。 ## クエリーを書いてみよう 1. ユーザーの情報を表示するProfileコンポーネント - このクエリーは、ユーザーのIDを指定してGraphQLエンドポイントに問い合わせているため、そのユーザーの ID、名前、メールアドレスが返ってくる ```graphql= { user(id : 1){ Id name email } } ``` 1. 記事、記事リンク、著者名を表示するPost - このクエリーは、記事のIDを送信することで、1件の記事のデータをリクエストしています。結果として、その記事のタイトル、リンク、著者名が返ってくる ```graphql= { post(id : 2){ title link author { name } } } ``` 1. ユーザーの詳細情報とそのユーザーが投稿した記事のタイトルを表示するAuthorコンポーネント - このコンポーネントには、ユーザーの情報と、そのユーザーが投稿した記事のタイトルが必要になる - このクエリは、あるユーザーのデータと、そのユーザーが投稿したすべての記事のデータを要求 - ただし、記事データのうち取得するのはタイトルのみ - そのユーザーが投稿した記事の行列が返されるが、各postオブジェクトには記事タイトルのみが含まれる ```graphql= { user(id : 2){ email name posts { title } } } ``` **これらに対してのクエリー文を書いてみよう** - ユーザー全員のデータと、各ユーザーが投稿した記事のタイトルを照会 ```graphql= { users{ id name email posts{ title } } } ``` > https://graphql-demo.mead.io/ > このサイトでクエリーを実行してみてください ## GraphQLのクエリー解決方法 - GraphQLAPIの実装に必要な処理は、ほとんどがバックエンドで行われる - 必要に応じてクライアントからクエリを実行してデータを取得できるようにするには、バックエンドからGraphQLエンドポイントをエクスポーズしなければならない - バックエンドで、クライアントに提供するデータをエクスポーズするインターフェイスを作成する必要がある - ブログAPIの例で考えてみましょう。ユーザーに関するデータと記事に関するデータがあります。この2つは別々のエンティティ - GraphQLでは、スキーマを作成してこの2つを定義します。 - 型はキーと値のペアで定義する - キーはエクスポーズしたいプロパティ、値は標準のGraphQLデータ型またはカスタム型 **User型とPost型を定義** ```graphql= type User { id: Int! name: String! email: String posts: [Post!] } type Post { id: Int! title: String! published: Boolean! link: String author: User! } ``` GraphQLには複数のデフォルト型が付属していて、最も一般的なのはスカラー型です 以下の5つのスカラー型を使って、APIが返すプロパティのデータ型を定義できる **スカラー型** - ID: 一意の識別子を使ってフィールドを定義する - Int: 符号付き 32 ビット整数 - Float: 符号付き倍精度浮動小数点数 - String: UTF-8 文字シーケンス - Boolean: true または false 先ほど定義したUser型とPost型はカスタム型です。User型のpostsプロパティが、カスタム型であるPost型に設定されています。これは、ユーザーが投稿した記事の行列を返します。またPost型のauthorプロパティは、記事を投稿した著者の詳細を返す 感嘆符 (!) はnullを許容しないフィールドであることを示し、感嘆符が付いていないフィールドはnullを返すことができます。 これらの型を照会するには、GraphQLのデフォルト型の1つであるQuery型を定義する必要があr Query型を使って、照会するデータポイントを定義できる たとえば、次のようなクエリを作成する ```graphql= { users { name email } } ``` Query 型の内部に users のデータ照会ポイントが定義されているので、Query型は一般的に以下のように定義する ```graphql= type Query { users: [User!]!, user(id: Int!): User! } ``` この定義では、2つの照会ポイントをエクスポートする まず、usersによってユーザーのコレクションがフェッチされ、userによって指定したIDのユーザー1名がフェッチされる ## Nodejs でシンプルな GraphQL サーバーを構築する 1. プロジェクトを開始する ```shell= mkdir graphql-server ``` 1. package.jsonを作成 ```shell= npm init -y ``` 1. package.json ```json= { "name": "graphql-backend-turorial", "version": "1.0.0", "description": "", "type": "module", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "npx tsc && nodemon --es-module-specifier-resolution=node dist/index.js" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "express-graphql": "^0.12.0", "graphql": "^15.5.1", "nodemon": "^2.0.12" }, "devDependencies": { "@types/express": "^4.17.13", "@types/node": "14", "ts-node": "^10.1.0", "tsconfig-paths": "^3.10.1", "typescript": "^4.3.5" } } ``` 1. 必要なライブラリをインストール ```shell= npm install express graphql express-graphql nodemon npm install typescript @types/express @types/node ts-node tsconfig-paths ``` 1. typescriptの設定 ```shell= npx tsc init ``` ```json= { "compilerOptions": { "target": "ESNEXT" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, "module": "es2015" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ "sourceMap": true /* Generates corresponding '.map' file. */, // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./dist" /* Redirect output structure to the directory. */, // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ /* Module Resolution Options */ "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, "paths": { "@src": ["./src"] } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */, // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "include": ["src/**/*"] } ``` 1. GraphQL サーバーを構築 - このファイルは、ユーザーデータのコレクションと各ユーザーの記事をエクスポート ```javascript= /* data.ts */ export const Users = [ { id: 1, name: "Fikayo Adepoju", email: "fik4christ@yahoo.com", posts: [ { id: 1, title: "Debugging an Ionic Android App Using Chrome Dev Tools", published: true, link: "https://medium.com/@coderonfleek/debugging-an-ionic-android-app-using-chrome-dev-tools-6e139b79e8d2", author: 1, }, { id: 2, title: "Hosting a Laravel Application on Azure Web App", published: true, link: "https://medium.com/@coderonfleek/hosting-a-laravel-application-on-azure-web-app-b55e12514c46", author: 1, }, ], }, { id: 3, name: "Jane Paul", email: "jane@company.com", posts: [], }, ]; ``` 1. スキーマを構築してエクスポート - このファイルでは、Node.jsのGraphQLライブラリにあるbuildSchemaメソッドを使用してスキーマをセットアップ - User型とPost型という2つのカスタム型を作成し、クエリ定義内でusersとuserの照会ポイントをエクスポートする ```javascript= /* schema.ts */ import { buildSchema } from "graphql"; export const schema = buildSchema(` type Query { users: [User!]!, user(id: Int!): User! } type User { id: ID! name: String! email: String posts: [Post!] } type Post { id: ID! title: String! published: Boolean! link: String author: User! } `); ``` 1. これらのクエリを処理するリゾルバを作成 ```javascript= /* resolvers.ts*/ import { Users } from "./data"; type Posts = { id: number; title: string; published: boolean; link: string; author: number; }; type Users = { id: number; name: string; email: string; posts: Posts; }; type Id = { id: number; }; export const resolvers = { users: async (_: Users) => { return Users; }, user: async ({ id }: Id, context: any) => { return Users.find((user) => user.id == id); }, }; ``` 1. スキーマをリゾルバに接続して、GraphQL エンドポイントをエクスポーズ - Expressアプリケーションを作成し、express-graphqlミドルウェアパッケージを使って、スキーマをリゾルバに接続した後、エンドポイント/graphqlでGraphQLAPI をエクスポート - 3つ目のパラメーターgraphiqlはtrueに設定、これでGraphiQLツールを利用できる - Graph**i**QL(**iが入ってるよ**)は、GraphQLクエリをテストするためのWebベースのGUI、このツールはGraphQLパッケージに含まれているらしい ```javascript= /* index.ts */ import express from "express"; import { graphqlHTTP } from "express-graphql"; import { schema } from "./schema"; import { resolvers } from "./resolvers"; const app = express(); app.use( "/graphql", graphqlHTTP({ schema, rootValue: resolvers, graphiql: true, }) ); const port = process.env.PORT || 4200; app.listen(port); console.log(`🚀 Server ready at http://localhost:4200/graphql`); ``` 1. GraphQL サーバーを実行 ```shell= node index.js ``` 1. localhost://4200/graphql でサーバーが実行中であることを示すコンソールメッセージが表示される ![](https://i.imgur.com/c9uSfsi.png) 1. クエリーウィンドウでクエリーを実行 - scheme.tsで定義したtype Query{} に沿ってクエリーを投げる ```graphql= { users { name email posts { title } } } ``` ```graphql= { user(id: 2) { id name email posts { id } } } ``` ## Postmanでクエリー投げてみた ![](https://i.imgur.com/1wbkD7V.png)