Next.js + Prisma2 + TypeScript + Now で型安全かつ爆速フルスタック開発する方法

フロントエンド大好き人間にとって一番苦なのはバックエンドの実装ですよね。少なくても自分はそうです。

そこで今日はNext.jsの[API Routes](https://nextjs.org/docs/api-routes/introduction)を使い、ちょー簡単にGraphQLのエンドポイントAPIを生やす方法を紹介します。

# はじめに

今回使用するライブラリは以下です

- Next.js
- Prisma2 w/ Photon & Lift
- GraphQL Nexus
- Apollo Server
- Apollo Client

# Prismaって何？

自分で説明するより、[こちらの記事](https://qiita.com/Quramy/items/5c5fc3bc69f2f9a91732)がまとまって説明してあるので、興味ある方は呼んでください。

簡単に言うと、`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アプリを作成する**

```bash
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でやります

```bash
yarn add @prisma/photon apollo-server-micro graphql nexus nexus-prisma

yarn add --dev prisma2 ts-node
```

`package.json`にも以下のスクリプトを追加してください。後ほど説明します。

```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`を作成し、以下のようなモデルを定義します。

```: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にログインして、

```bash
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`用にも定義します。

```js
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側の整合性が保てなくなり今はコネクションが張れないみたい。詳しくは[こちら](https://github.com/prisma/prisma2/issues/1170#issuecomment-574184501) で追ってください。でもこうすることにより`next dev`はAPIサーバーに接続できなくなるので、`now dev`を使ってnowの環境をローカルで再現して開発してください。はよ治ってほしい）

その中に、`graphql.ts`,`_src/schema.ts`,`_src/context.ts`を作成

```
├── api
│   ├── _src
│   │   ├── context.ts
│   │   └── schema.ts
│   └── graphql.ts
```

こんな感じ。`_`でプリフィックスされたファイルはサーバーレスエンドポイントとしてハンドルされないらしい。

```ts:api/_src/context.ts
import { Photon } from '@prisma/photon';

const photon = new Photon()

export interface Context {
  photon: Photon
}
// apolloServerでphotonをcontextとして渡すと、のちにnexus側で使用可能になる
export const createContext = () => ({ photon })
```

```ts:api/_src/schema.ts
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"
      }
    ]
  }
})
```

```ts:api/graphql.ts
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`を作成してください。そして、その中に、

```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は今現在ではまだプレビュー段階です。開発の進捗は[こちら](https://www.notion.so/Is-Prisma-2-Ready-8b3fba3eaf5b4bf3ab7102fd94f56148)で確認できます

一つのリポジトリで、monorepoなどにする必要がなく、`now`コマンド一つでバックエンドとフロントエンドをデプロイできるのって、すごい世界だなって思います。

githubとnowを連携させると、PRごとにデプロイしてくれるようになり、検証用URLもくれるので、簡単に違いをチェックできたりできます。今年はフロントエンジニアにとっては最高な年になりそうな予感。

Zeit創業者であるGuillermo Rauchさんのこのブログ記事が、今後のウェブ開発の展望など事細かく書いてあるので、おすすめです。

https://rauchg.com/2020/2019-in-review

終わり。