📈

Apollo Server + Nexus + PrismaでGraphQL開発: 環境構築・初期設定

2021/12/02に公開

この記事は、いかずちさんだー Advent Calendar 2日目の記事です。

趣旨

前回の記事では、どんなフレームワーク・ライブラリを使うのかを簡単に説明しました。
今回は、これらを使った開発のための環境構築・初期設定を見ていきたいと思います。

環境構築

必要なnpm iが書かれていない場合もあると思うので、雰囲気に従って補完してください。
被ってる場合もあるので、そっちの場合は省いてください。

DevContainerの構築

DevContainerをいい感じに設定します。
前回の記事に書いたとおり、ほとんどMicrosoftが用意したデフォルトのままでよいでしょう。
必須クラスで追加すべき拡張は、prisma.prismaくらいです。
それ以外はお好みで設定してください。

TypeScriptの設定

ESLintやPrettierの設定を行います。
また、.vscode/setting.jsonにオートフォーマットまわりの設定なども書いておきましょう。
このあたりの設定は新しくやる時期ごとにまったく変わっているという印象なので、各自で最新の記事を参照しましょう。
ぼくはこれを見ながらやりましたが最新なのか自信がありません。

https://zenn.dev/teppeis/articles/2021-02-eslint-prettier-vscode

Debug実行の設定

Nexusを用いた開発では、実行時に型情報が生成されるため、高頻度でDebug実行を行うことになります。
型情報が生成されるのは実行後なので、型チェックなしで実行できるような設定を書いておきましょう。
参考までに、ぼくたちが用いている設定はこちらになります。

.vscode/launch.json (抜粋)
{
  "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の設定

https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases-typescript-postgres

npm i prisma --save-dev で入手します。
その後、以下のようにDB接続設定を書きます。

prisma/schema.prisma
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}
.env
DATABASE_URL="mysql://user:pass@hostname:port/dbname"

デフォルトで.envに対応しているので、DB設定は.envに書くとよいでしょう。
その必要がなければもちろん直接書いてもかまいません。

この状態でnpx prisma db pullを行うと、schema.prismaにテーブル情報が書き込まれます。
relationやindex、unique制約の情報なども書かれているのを確認しましょう。

その後、以下を追記します。一番上がいいと思います。

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

npx prisma generateをすると、ORM用の型情報が生成されます。
@prisma/clientnpm iせよみたいな指令も下ると思うので、それも行えば、本文中でデータベースアクセスができるようになります。

[Optional] nexus-prismaの設定

https://nexus.prisma.io/usage

追加でnpm i nexus-prisma nexus graphqlして入手します[3]

prisma/schema.prisma
generator nexusPrisma {
   provider = "nexus-prisma"
}

を追記し、再度npx prisma generateすることで、Nexusで使える型情報が生成されます。

初期設定

Apollo Server + Nexusを動かす

https://www.apollographql.com/docs/apollo-server/getting-started/

https://nexusjs.org/docs/getting-started/tutorial/chapter-setup-and-first-query

今回は、様々な事情を勘案してExpressに乗っかる形でApollo Serverを動かします。
npm i apollo-server express nexus graphqlとして入手します(前節と重複があるので省いてもOKです)。

Nexusで作ったスキーマを作る

まず、深いことを考えず非常にシンプルなクエリを定義してみましょう。
これは、必ず「Hello World」と返すクエリです。

src/schema/hello.ts
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します。

src/schema/index.ts
export * from './hello'

それを、schema.tsで受け取ってApollo Serverに流す用の変換をします。

src/schema.ts
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でスキーマを受け取って起動する

src/main.ts
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クライアントを持たせるようにしてみましょう。

src/context.ts
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のコードの中で型推論が有効になります。

src/schema.ts
  types: [
    allTypes,
  ],
+ contextType: {
+   module: join(__dirname, './context.ts'),
+   export: 'Context',
+ },
})

一度サーバを起動し直して型情報を生成したのちに、Nexusのコードを少し触ってみましょう。

src/schema/hello.ts
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を設計していくことにします。
引き続きよろしくお願いします。

脚注
  1. これに従う場合はnpm i ts-node-dev --save-devしてください。 ↩︎

  2. Windows機(Ryzen 5600X)では満足いく速度です。Docker for Macが悪いとかもありそう。 ↩︎

  3. nexus-prisma以外はどうせ必要です ↩︎

GitHubで編集を提案

Discussion