Apollo Server + Nexus + PrismaでGraphQL開発: 環境構築・初期設定
この記事は、いかずちさんだー Advent Calendar 2日目の記事です。
趣旨
前回の記事では、どんなフレームワーク・ライブラリを使うのかを簡単に説明しました。
今回は、これらを使った開発のための環境構築・初期設定を見ていきたいと思います。
環境構築
必要なnpm i
が書かれていない場合もあると思うので、雰囲気に従って補完してください。
被ってる場合もあるので、そっちの場合は省いてください。
DevContainerの構築
DevContainerをいい感じに設定します。
前回の記事に書いたとおり、ほとんどMicrosoftが用意したデフォルトのままでよいでしょう。
必須クラスで追加すべき拡張は、prisma.prisma
くらいです。
それ以外はお好みで設定してください。
TypeScriptの設定
ESLintやPrettierの設定を行います。
また、.vscode/setting.json
にオートフォーマットまわりの設定なども書いておきましょう。
このあたりの設定は新しくやる時期ごとにまったく変わっているという印象なので、各自で最新の記事を参照しましょう。
ぼくはこれを見ながらやりましたが最新なのか自信がありません。
Debug実行の設定
Nexusを用いた開発では、実行時に型情報が生成されるため、高頻度でDebug実行を行うことになります。
型情報が生成されるのは実行後なので、型チェックなしで実行できるような設定を書いておきましょう。
参考までに、ぼくたちが用いている設定はこちらになります。
{
"name": "Debug",
"type": "node",
"request": "launch",
"protocol": "inspector",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/ts-node-dev",
"args": ["--transpile-only", "${workspaceRoot}/src/main.ts"],
"restart": true,
"env": {
"NODE_ENV": "local_dev"
}
}
ts-node-dev
を用いてホットリロードが発生するようにしていますが、最近はこれはなくてもいいかもしれないと考え始めています(手動で再起動することも多い)。[1]
--transpile-only
は必須で、型生成前でも動作するようになるほか、起動速度も改善します。
それでもMacBook Pro(15-inch, 2019, Corei9-9880H) だと起動がややもったりしますが…[2]。
Prismaの設定
npm i prisma --save-dev
で入手します。
その後、以下のようにDB接続設定を書きます。
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
DATABASE_URL="mysql://user:pass@hostname:port/dbname"
デフォルトで.envに対応しているので、DB設定は.envに書くとよいでしょう。
その必要がなければもちろん直接書いてもかまいません。
この状態でnpx prisma db pull
を行うと、schema.prisma
にテーブル情報が書き込まれます。
relationやindex、unique制約の情報なども書かれているのを確認しましょう。
その後、以下を追記します。一番上がいいと思います。
generator client {
provider = "prisma-client-js"
}
npx prisma generate
をすると、ORM用の型情報が生成されます。
@prisma/client
をnpm i
せよみたいな指令も下ると思うので、それも行えば、本文中でデータベースアクセスができるようになります。
[Optional] nexus-prismaの設定
追加でnpm i nexus-prisma nexus graphql
して入手します[3]。
generator nexusPrisma {
provider = "nexus-prisma"
}
を追記し、再度npx prisma generate
することで、Nexusで使える型情報が生成されます。
初期設定
Apollo Server + Nexusを動かす
今回は、様々な事情を勘案してExpressに乗っかる形でApollo Serverを動かします。
npm i apollo-server express nexus graphql
として入手します(前節と重複があるので省いてもOKです)。
Nexusで作ったスキーマを作る
まず、深いことを考えず非常にシンプルなクエリを定義してみましょう。
これは、必ず「Hello World」と返すクエリです。
import { queryType } from 'nexus'
export const hello = queryType({
definition(t) {
t.nonNull.string('hello', {
resolve: () => 'Hello world!'
})
},
})
(ちなみに、これから自動生成されるスキーマは以下のようなものです。)
type Query {
hello: String!
}
NexusのスキーマをApollo用に変換する
先ほど作ったNexusスキーマを、index.tsで一括exportします。
export * from './hello'
それを、schema.tsで受け取ってApollo Serverに流す用の変換をします。
import { makeSchema } from 'nexus'
import * as allTypes from './schema/index'
export const schema = makeSchema({
outputs: {
schema: __dirname + '/generated/schema.graphql',
typegen: __dirname + '/generated/nexus-typegen.ts',
},
types: [
allTypes,
],
})
ここで、スキーマと型情報の生成先を指定しているのを確認しておきましょう。
nexus-typegen.ts
に生成された型情報は、今後の開発で活用することになります。
Apollo Serverでスキーマを受け取って起動する
const server = new ApolloServer({
schema: schema,
})
await server.start()
const app = express()
server.applyMiddleware({ app })
console.log(`🚀 Server ready at http://127.0.0.1:8080${server.graphqlPath}`)
await new Promise(() => app.listen(8080))
これで、起動が可能な形になりました。
F5を押して起動すると、スキーマと型情報が生成され、Apollo Serverが起動します。
動作確認
http://localhost:8080/graphql にアクセスすると、いまからApollo Studioを開くぜ的画面が表示されるので、開いてみましょう。
query {
hello
}
とすれば、
{
"data": {
"hello": "Hello world!"
}
}
という返答が帰ってくるはずです。
Apollo ServerにContextを設定する
contextを持たせることで、Nexusの処理の途中でDBアクセスができるようになったり、認証・認可を行ったり、アクセス者の情報を保持したりできるようになります。
ここはシンプルに、contextにPrismaクライアントを持たせるようにしてみましょう。
const prisma = new PrismaClient({
log: [{ level: 'query', emit: 'event' }],
})
prisma.$on('query', (e) => {
console.log(e)
})
export type Context = {
prisma: PrismaClient
}
export const context = async ({ req }: ExpressContext) => { prisma: prisma }
schema.ts
の中で、contextの型情報を入力することで、Nexusのコードの中で型推論が有効になります。
types: [
allTypes,
],
+ contextType: {
+ module: join(__dirname, './context.ts'),
+ export: 'Context',
+ },
})
一度サーバを起動し直して型情報を生成したのちに、Nexusのコードを少し触ってみましょう。
import { queryType } from 'nexus'
export const hello = queryType({
definition(t) {
t.nonNull.string('hello', {
- resolve: () => 'Hello world!'
+ resolve: (_root, _args, ctx) => {
+ ctx.prisma. // 補完が有効になっており、テーブル名などが候補になる
+ },
})
},
})
ctxの型推論がうまくいくようになっているかと思います。
今後はこれを利用して、Nexusの内部でDBアクセスを行っていくことになります。
ほかにも、contextの設定の段階ではリクエストヘッダ等の情報を受け取ることができるので、必要に応じて利用していきましょう。
おわりに
今回は、本格的にGraphQLのコードを書いていくまでの間にやるべき設定・構築について解説しました。
ここまでである程度の雰囲気はつかめていただけるのではないかと思います。
次回は、もっと本格的にGraphQLを設計していくことにします。
引き続きよろしくお願いします。
Discussion