Next.js + typescript + Prisma の API テンプレートを作る

## やりたいこと

API は、案件ごとに実装方法が変わったりしないので、何度も共通部分を実装しなおしたり、コピペするのは時間の無駄。

API を作成する案件では、API だけでなく API Documents も納品する必要があったりする。openapi.yaml だけ納品して、後は好きにして。で OK なことがあんまりない。ブラウザでアクセスできる状態で納品してと言われる。Basic認証付きで。サーバも用意されてないのに。なんならモックも用意してと言われる。サーバもないのに。


を解決したい。

- Bearer 認証付き API の基本系を準備する(```Next.js```, ```TypeScript```, ```Prisma```)
- API の基本形をボイラープレートする（```scaffdog```)
- openapi.yaml を元にした API Documents を Basic 認証かけて組み込む (```@stoplight/elements```, ```Next.js12::middleware```)
- docker で動くようにする

## できたもの

https://github.com/TAKT-R-D/next-ts-api-template

## システム構成
- Next.js: 12.2+
- Prisma: 4.x


## Bearer 認証付き API の基本形を準備する

> [Next.js(TypeScript) で Prisma を使って API を作る](https://qiita.com/kurab/items/efce37b19f4484ae39bc)

各種バージョンが違うが、これを基本にする。Prisma のメジャーバージョンが上がっているが、今回の内容程度ではほぼ影響がない。
どこまで作るか？は悩ましいが、テンプレートなので、API としては、
- [POST] auth/register : ユーザ登録
- [POST] auth/access_token : アクセストークンを取得
- [GET] users : ユーザ一覧を取得
- [GET, PUT, DELET] users/{userId} : ユーザ情報を取得・更新・削除

これくらい作っておくことにした。認証は Json Web Token を使っているが、もっと変えても良い。テンプレートなので、いったん JWT のままにしておく。

```
├── handlers   // Cors, Bearer 認証
├── lib        // 共通で使うもの
├── pages
│   └── api    // 具体的な API
├── prisma     // schema 定義
└── types      // type
```

以前の投稿で説明が足らないなと思うのは、relation だが、今回の内容とはずれるので、別の機会に。

## scaffdog で一括生成する

API のエンドポイントは、どのようにするのが分かりやすいか？使うメソッドは何が良いのか？など、正解はない気がするが、重要なのは、分かりやすさであり、一貫性であると思う。

という訳で、API の基本形は、例えば、articles API を作る場合、

```
pages/api
└── articles
    ├── index.ts
    └── [articleId].ts
```

を作り、

- index.ts
    - GET: 一覧を取得
    - POST: 登録
- [articleId].ts
    - GET: 詳細を取得
    - PUT: 更新
    - DELETE: 削除

とする。レスポンスは、

- 共通
    - 400 Bad Request （バリデーションエラー）
    - 401 Unauthorized （認証エラー）
    - 406 Not Acceptable （認証エラー）
    - 500 Internal Server Error （システムエラー）
- GET
    - 200 OK （正常系）
    - 204 No Content （0件など）
    - 404 Not Found （slug に該当するレコードがない場合）
- POST, PUT
    - 200 OK （正常系）
    - 404 Not Found （親・対象がない場合）
- DELETE
    - 204 No Content （正常系）

とした。201 とか 409 とかあるけど、いったんこれで。
こういう基本形があると、openapi.yaml を書く時も楽。
scaffdog は、マークダウンでひな形を書くツールで、一括で複数ファイルを作ってくれる。使い方は簡単で、ドキュメントで十分な情報が得られる。

https://scaff.dog/docs

```
$ yarn scaffdog generate
```

と打つと、対話式で作成してくれる。

![Screen Shot 2022-08-15 at 2.47.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/7393341c-48fc-7d37-81d7-98e1934862ba.png)


これをやる前（後でも良いけど）に、Prisma の model を作成しておく必要があり、上記の例だと、```Article``` という名前の model を作成する。

```schema.prisma
model Article {
  id        Int      @id @default(autoincrement())
  title     String
  body      String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("articles")
}
```

こんな感じで。
特筆すべきことはあまりないが、
slug は、プログラムで取得すると、string で取れるので、where にそのまま入れると TypeError になるため、どっちなの？と聞いて処理を分けている。
また、上記例では、```types/ArticleType.ts``` というファイルができているが、relation を実装していく上で必要になるため、型定義をして組み込んでいる。relation がない場合、必要ないが relation が全くないものもあんまりないので、作っておいた。

```types/ArticleType.ts
import { Prisma } from '@prisma/client';
import type { Article } from '@prisma/client';

export type ArticleResponseType = Article & {}
export type ArticlePostPropsType = Prisma.ArticleCreateInput & {}
export type ArticlePutPropsType = Prisma.ArticleUpdateInput & {}
```
こんなのができる。```{}``` に、relation で使うものを書いていくことになる。今回は書かない。

コマンド打つだけで、素の API は出来上がる。ここまで決まっていればコピペ + 置換でも実際は大差ないが。大差ないので、こっちのほうが良い。



## API Documents を組み込む

openapi は、stoplight studio などを使って書くもよし、自分で書くもよし。好きに書いていただいて良い。小規模な API の場合は、ツールを使って書くのが良いが、大規模になってくると、手動で書くほうが良い。気がする。

```/docs``` の下に openapi.yaml が置いてあり、```dist``` ディレクトリがあるが、dist に merge したものを吐き出すつもりで、そんな構成になっている。merge は、 ```/scripts/build_openapi.sh``` を叩けば merge される。

API Documents は、localhost:3000/docs にアクセスしたらベーシック認証がかかった状態で、表示されるようにしたい。

openapi.yaml から API Documents を生成するモジュールはいくつかあるが、今回は、```@stoplight/elements``` を利用した。モックで Prism 使うし、stoplight studio も使うし、揃えちゃって良いかなと思って。

yarn dev すると Warning が出たり、オプションがきちんと動かなかったり、あんまりお勧めはしないが、build すれば問題ないし、Production には出さないつもりなので、良いことにした。

![Screen Shot 2022-08-15 at 1.37.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/9765e25b-c44e-cf74-8075-62cc462bf88d.png)

こういうのが出る。stoplight studio と見た目が違うのかよ！と思ったけど。

実装は簡単で、

```/pages/docs/index.tsx
import type { NextPage } from 'next';
import React from 'react';
import Head from 'next/head';
import { API } from '@stoplight/elements';
import '@stoplight/elements/styles.min.css';

const ApiDocuments: NextPage = () => {
  return (
    <div>
      <Head>
        <title>API Documents</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <API
        apiDescriptionUrl="/elements/ZJ6wMNW4Md4NEu.yaml"
        basePath="/docs"
        router="memory"
      />
    </div>
  );
};

export default ApiDocuments;
```

これだけ。

```typescript
      <API
        apiDescriptionUrl="/elements/ZJ6wMNW4Md4NEu.yaml"
        basePath="/docs"
        router="memory"
      />
```

ここの、```apiDescriptionUrl``` は、 ```/public/elements``` に入っている。```/docs/dist/``` は参照できないため、public に入れた。public ディレクトリなので、何となく分かりにくい名前が良いかなと、こんな名前になっている。

```/scripts/build_openapi.sh``` を叩けば、public の方にも吐き出される。

```router``` は、memory 以外ではうまく動かなかった。
@stoplight/elements は活発に開発が行われているので、そのうち直るんじゃないかと。


### Basic 認証
次に、Basic 認証。Next.js では、middleware を使って Basic認証するが、バージョンが 12 になって Middleware の扱いが大きく変わった。詳細は割愛するが、いままでは pages の下などにあった _middleware.ts がルートディレクトリに1つだけ middlewre.ts を置くようになった。詳細は[こちら](https://nextjs.org/docs/messages/middleware-upgrade-guide)。

Next.js v12 で Basic 認証は、以下のように書く。

```/middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export const config = {
  matcher: '/docs',
};

export function middleware(req: NextRequest) {
  const basicAuth = req.headers.get('authorization');
  const url = req.nextUrl;

  if (basicAuth) {
    const authValue = basicAuth.split(' ')[1];
    const [user, password] = atob(authValue).split(':');

    if (
      user === process.env.BASIC_AUTH_USER &&
      password === process.env.BASIC_AUTH_PASSWORD
    ) {
      return NextResponse.next();
    }
  }
  url.pathname = '/api/basicAuth';

  return NextResponse.rewrite(url);
}
```

```/pages/api/basicAuth.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(_: NextApiRequest, res: NextApiResponse) {
  res.setHeader('WWW-authenticate', 'Basic realm="Secure Area"');
  res.statusCode = 401;
  res.end(`Auth Required.`);
}
```

なお、
```typescript
    const [user, password] = atob(authValue).split(':');
```
この部分は、atob は非推奨だから

```typescript
    const [user, password] = Buffer.from(authValue).toString().split(':');
```

こう書けと言われるが、こう書くと、

```
./middleware.ts
A Node.js API is used (Buffer at line: 10) which is not supported in the Edge Runtime.
Learn more: https://nextjs.org/docs/api-reference/edge-runtime
```

と出る。残念。

## docker で動かす

最後に docker であるが、docker は特別変わったことはやっていないが、Prisma で binaryTarget を追加しないと動かないことがある。

```schema.prisma
generator client {
  provider = "prisma-client-js"
  previewFeatures = ["interactiveTransactions"]
  binaryTargets = ["native", "linux-musl"]
}
...
```

リナックスまっする！

## 使い方
テンプレートなので。

```bash
$ npx create-next-app some_api --example https://github.com/TAKT-R-D/next-ts-api-template
```

便利！！



## やらなかったものなど

dredd とは仲良くなれなかったので、今回は諦めた。
mock サーバは、Prism を使って立ち上げるスクリプトを ```/scripts/run_moch.sh``` においたが、コンテナは作らなかった。

また、当初、openapi.yaml から API を自動生成する方向で考えていたが、結局辛いのは、openapi.yaml の方なので、そっちを自動生成する方向で再検討する。scaffdog でテンプレート書けばできちゃいそうだけど。そこまでできれば、API 作成は、基本 schema の定義するだけでできるようになる。


## 最後に
基本的に作業に落とし込んだものは、自分では実装せず、メンバーに依頼するのだが、「articles api を参考に実装して」という頼み方をすると、なんか書き方を変えてきたり、いや、俺は 409 が入ってた方が良いと思う、などと別の処理を入れてきたりする。レビューが面倒だし、テストも必要になる。API 仕様書も書き換えないといけなくなるし、作業が増える。増えるというか、このまんま使ってもらえれば、レビューもテストも基本必要なくなるのが良いところなので、極力アレンジの余地がないようにしたい。更にここまで出来ていれば依頼するメンバーはエンジニアである必要がない。掃除のおばちゃんでもできるし、中学生の息子でもできる。

もちろん、これで完成はしないので、エンジニアが不要にはならないが、世界中のエンジニアの1％の工数が削減できれば、それは20万人分の工数に匹敵するので、まぁ100万人分くらいは削減できるかもしれない笑。会社的には、相当なコストカットになる。

弊社は、そんなようなことをやっている会社です。てっていてきに。

以上、おしまい。