2度目のApollo(昔話)に書いたように、Apollo GraphQLを使ってのバックエンド構築を検討しています。今回は技術的な記事です、と言うかかApollo GraphQLサーバー構築・評価の記録です。
まずは、公式ドキュメントを軽く眺めた後、QiitaやBlogを参考に環境作りを始めました。
環境のベース
すっかりDockerにもなれ、Docker ComposeでササッとPostgreSQL、Expressサーバーの環境を作り、ts-node + ts-node-devを使いTypeScriptでサーバーを書ける環境ができました。その上で以下のような簡単なApollo GraphQLサーバーが動きました。
- src/index.ts
import express from 'express';
import cors from 'cors';
import { ApolloServer, gql } from 'apollo-server-express';
import { Pool } from 'pg';
const schema = gql`
type Query {
user(id: Int!): User
users: [User]
}
type User {
id: Int!
name: String!
email: String!
group_id: Int!
created_at: String
}
`;
const resolvers = {
Query: {
user: async (_root: any, {id}: {id:number}, context:any) => {
const client = await context.pg.connect()
const { rows } = await client.query('SELECT * FROM users WHERE id= $1', [id]);
return rows[0];
},
users: async (_root: any, _args: any , context:any) => {
const client = await context.pg.connect()
const { rows } = await client.query('SELECT* FROM users');
return rows;
}
},
};
const app = express();
app.use(cors());
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_DATABASE,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});
(async () => {
const server = new ApolloServer({
typeDefs: schema,
resolvers: resolvers,
context: () => ({ pg: pool})
});
await server.start()
server.applyMiddleware({ app });
})()
app.listen({ port: 5000 }, () => {
console.log('server on http://localhost:5000/graphql');
});
簡単に解説すると、
- schemaにGraphQLのAPI定義、データの型定義を設定します
- resolversにAPIの処理コードを書きます
- Apolloの初期化処理
new ApolloServer()
ではschema, resolversに加えresolversの処理で使うリソースをcontextに渡します - その他はExpressやnode-postgresの設定などです
Apollo GraphQLサーバーの型定義
上のコードのresolverにはany
型が頻発しています😅
Apollo GraphQLの公式ドキュメントはJavaScriptベースなので、とりあえず型が判らない部分はanyで進めました。
GraphQLのschemaには以下のように型定義があるのですが、それをTypeScriptから利用する事はできないのです・・・
type User {
id: Int!
name: String!
email: String!
group_id: Int!
created_at: String
}
いろいろ調べるとGraphQL Code Generatorを使うと上のようなGraphQLの定義から下のようなTypeScriptの定義ファイルを作ってくれます。
export type User = {
__typename?: 'User';
id: Scalars['Int'];
name: Scalars['String'];
email: Scalars['String'];
group_id: Scalars['Int'];
created_at: Scalars['String'];
};
さらに、 export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> = ResolverFn<TResult, TParent, TContext, TArgs> | ResolverWithResolve<TResult, TParent, TContext, TArgs>;
のようなresolverの型定義も生成してくれるので any
を止める事もできました。
ただし、GraphQL Code Generatorの設定では以下のようにインタラクティブな質問に答えるのですが、GraphQL初心者には判らない質問があり困りました。
$ npx graphql-codegen init
? What type of application are you building? Backend - API or server
? Where is your schema?: (path or url) http://localhost:5000/graphql
? Pick plugins: TypeScript (required by other typescript plugins),
TypeScript Resolvers (strongly typed resolve functions)
? Where to write the output: src/generated/graphql.ts
? Do you want to generate an introspection file? No
? How to name the config file? codegen.yml
? What script in package.json should run the codegen? codegen
DateTime型が使いたいのですが
上のGraphQLの型定義ではcreated_atはString型で定義していますが、本来DateTime(日時)型を使いたいです。
しかしGraphQLのデフォルトのスカラー型は、Int, String, Float, Boolean, IDしかありません。GraphQL(Apollo)は独自のスカラー型を定義できるようになっていいます → Apollo Docs::Custom scalars。
ただし自分で作るのは面倒そうです。しらべるとGraphQL Scalarsという独自型を集めたライブラリーがありました! もちろんDateTimeもありました。
めでたしめでたし😁 次回へ続きます・・・・