フロントエンド大好き人間にとって一番苦なのはバックエンドの実装ですよね。少なくても自分はそうです。
そこで今日はNext.jsのAPI Routesを使い、ちょー簡単にGraphQLのエンドポイントAPIを生やす方法を紹介します。
はじめに
今回使用するライブラリは以下です
- Next.js
- Prisma2 w/ Photon & Lift
- GraphQL Nexus
- Apollo Server
- Apollo Client
Prismaって何?
自分で説明するより、こちらの記事がまとまって説明してあるので、興味ある方は呼んでください。
簡単に言うと、schema.prismaに書かれたデータベースのスキーマをベースにいい感じにORMを自動生成してくれるやつです。
上の記事でも書いてある通り、まだpreviewなので、本番環境には適していません。遊ぶ程度でどうぞ。
GraphQL Nexusって何?
GraphQL NexusはPrisma社が開発したツールであり、code-firstなgraphQLのスキーマをタイプセーフかつスケール可能にしてくれます。
従来のGraphQLサーバーの構築法は、SDL(Schema Definition Language)ファーストのアプローチが基本でした。.graphqlで終わるファイルのことですね。まずスキーマを定義して、それに対するリゾルバを別で書く。TypeScriptを使用してる場合はさらにスキーマの型を書く必要がありました。何が起こるかと言うと、スキーマを少し変更するとそれに依存する他のファイルも変更する必要があり、ある程度アプリが大きくなっていくとスケーラビリティに欠けてしまいます。
code-firstと言うことは、スキーマはコード上で定義され、SDLは自動生成されるので、スキーマを変えるごとに他のファイルも編集する、という手間が省けます。詳しくは後ほどコードで。
開発
create-next-appでNext.jsアプリを作成する
npx create-next-app next-prisma
これで簡単なNext.jsアプリが作成されました。今回はTypeScriptを使用した開発なので、とりあえずpages/index.jsをindex.tsxに変更しnpm run devしてみてください。Next.jsが拡張子が変わったのを検知し、tsconfig.jsonを自動で作成してくれます(便利)。そしてtypescript, @types/react, @types/nodeをインストールしろとエラーが出るのでやってください。
モジュールのインストール
npm/yarn どちらでもいいです。create-next-appがyarnで初期モジュールをインストールするので、参考コードはyarnでやります
yarn add @prisma/photon apollo-server-micro graphql nexus nexus-prisma
yarn add --dev prisma2 ts-node
package.jsonにも以下のスクリプトを追加してください。後ほど説明します。
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"generate:prisma": "prisma2 generate",
"generate:nexus": "ts-node --project tsconfig-api.json --transpile-only api/_src/schema",
"generate": "yarn generate:prisma && yarn generate:nexus",
"postinstall": "yarn generate"
}
prismaファイルの作成
ルートディレクトリにprismaファイルを作成します。
その中にschema.prismaを作成し、以下のようなモデルを定義します。
generator photon {
provider = "photonjs"
}
datasource db {
provider = "postgresql"
url = env("PG_URL")
}
model User {
id Int @id
email String @unique
name String?
}
datasourceのproviderはpostgresqlとしてますが、mysqlやmongodbでも大丈夫です。好きなやつをどうぞ
envは.envなどで定義した環境変数をみにいくので、ローカルと本番でDB分けたりできます。Nowに環境変数を追加するには、ターミナルでnowにログインして、
now secrets add pg-url "postgresql://USERNAME:PASSWORD@localhost:PORT/postgres?SCHEMA"
# now secrets add <secret-name> <secret-value>
と打つと設定できます。herokuなどで簡単にpostgresDBを作成可能です。参照するにはnext.config.jsを作成し、以下のように定義します。prisma2はbuild timeに環境変数を参照するので、build用にも定義します。
exports.default = {
env: {
PG_URL: '@pg-url'
},
build: {
env: {
PG_URL: '@pg-url'
}
}
}
この段階で、yarn generate:prismaを叩きます。これでnode_modules/@prisma/photonにスキーマをもとに作成されたコードが生成されます。これにより、photon APIの基盤が生成されて、photon.users.findMany({})みたいなAPIが使用可能となります。
ちなみに、prisma2 devというコマンドもあり、叩くとPrismaがスキーマファイルを監視してくれて、変更を加えると自動でphotonを再生成し、データベースのスキーマをアップデートし、そしてPrisma StudioというGUIも用意してくれます。GUIで直接レコードを編集したりできちゃいます。便利。
GraphQLサーバーの構築
データレイヤーであるPrismaの方ができたので、ORMをアクセスするAPIレイヤーのGraphQLサーバーを構築しましょう。
イメージ的には、サーバーが二つある感じ。
Next.js <----> **GraphQL Server** <----> Prisma
まず、apiフォルダをルートに作成
(普通、Next.jsでは/pages直下にapiフォルダを作るのだが、nowにデプロイするとphoton側とNext.js側の整合性が保てなくなり今はコネクションが張れないみたい。詳しくはこちら で追ってください。でもこうすることによりnext devはAPIサーバーに接続できなくなるので、now devを使ってnowの環境をローカルで再現して開発してください。はよ治ってほしい)
その中に、graphql.ts,_src/schema.ts,_src/context.tsを作成
├── api
│ ├── _src
│ │ ├── context.ts
│ │ └── schema.ts
│ └── graphql.ts
こんな感じ。_でプリフィックスされたファイルはサーバーレスエンドポイントとしてハンドルされないらしい。
import { Photon } from '@prisma/photon';
const photon = new Photon()
export interface Context {
photon: Photon
}
// apolloServerでphotonをcontextとして渡すと、のちにnexus側で使用可能になる
export const createContext = () => ({ photon })
import { makeSchema, objectType } from 'nexus'
import { nexusPrismaPlugin } from "nexus-prisma";
import { join } from 'path';
// __dirname使えない問題はこれで解決。詳細はこちら
// https://github.com/prisma/prisma2/issues/1021
const getPath = (fileName: string) =>
join(process.cwd(), "generated", fileName);
// objectTypeでタイプを作る。なんでも作れるが、prisma側とあわせて作るとmodel APIを提供してくれて型を補給してくれるので、便利
const User = objectType({
name: "User",
definition(t) {
t.model.email();
t.model.id();
}
})
const Query = objectType({
name: "Query",
definition(t) {
t.crud.user();
t.crud.users();
}
})
const Mutation = objectType({
name: "Mutation",
definition(t) {
t.crud.createOneUser({
alias: 'createUser'
})
}
})
export const schema = makeSchema({
types: [User, Query, Mutation],
plugins: [nexusPrismaPlugin()],
outputs: {
typegen: getPath('nexus.ts'),
schema: getPath('schema.graphql')
},
typegenAutoConfig: {
contextType: "Context.Context",
sources: [
{
source: "@prisma/photon",
alias: "photon"
},
{
source: require.resolve("./context"),
alias: "Context"
}
]
}
})
import { ApolloServer } from 'apollo-server-micro';
import { createContext } from './_src/context';
import { schema } from './_src/schema';
const apolloServer = new ApolloServer({ schema, context: createContext() })
export const config = {
api: {
bodyParser: false
}
};
export default apolloServer.createHandler({ path: '/api/graphql' });
ここで、yarn generate:nexusを叩く前に、一旦ベースのtsconfig.json以外に、もう一つのts-node用のtsconfig-api.jsonを作成してください。そして、その中に、
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}
と記述してください。なぜかと言うと、next devをする際に、next側はmodule": "esnext"ではないとコンパイルしてくれないのですが、esnextだとts-nodeでコンパイルしようとしてもコケてしまいます。なので、ts-nodeでコンパイルするときだけ、専用のtsconfig-api.jsonを使用します。スクリプトの方で、--project tsconfig-api.jsonと指定しています。
yarn generate:nexusを叩いてください。うまくいくと、ルートにgeneratedというファイルが生成されると思います。その中にはnexus側が内部的に使用するnexus.tsと、SDLが記述されたschema.graphqlがあります。このように、普段は自分で書くはずのgraphqlファイルが、nexusによって自動で生成されるようになります。今の段階では単純にUserのtypeしかありませんが、modelが増え、肥大化していくとその恩恵を受けます。
これで一通り終了です。now devを叩いてみてください。/api/graphqlのエンドポイントに行くと、graphql playgroundが開いていると思います。あとは、クライアント側でapollo clientを使うなりして、好きなようにデータを受け渡しできます。
最後に
prisma2は今現在ではまだプレビュー段階です。開発の進捗はこちらで確認できます
一つのリポジトリで、monorepoなどにする必要がなく、nowコマンド一つでバックエンドとフロントエンドをデプロイできるのって、すごい世界だなって思います。
githubとnowを連携させると、PRごとにデプロイしてくれるようになり、検証用URLもくれるので、簡単に違いをチェックできたりできます。今年はフロントエンジニアにとっては最高な年になりそうな予感。
Zeit創業者であるGuillermo Rauchさんのこのブログ記事が、今後のウェブ開発の展望など事細かく書いてあるので、おすすめです。
終わり。