🍈

Next.jsにVercel MarketplaceでNeonのPostgresを導入する

に公開

はじめに

Next.jsにVercel MarketplaceでNeonのPostgresを導入する方法を投稿します。筆者はサイコロを使ったパズルゲームを開発して、データベースから世界最高得点を読み書きするようにしました。

本記事ではNext.jsのプロジェクトがVercelにデプロイされた状態から、Vercel MarketplaceでNeonのPostgresを導入する方法を説明しています。

筆者の開発環境

  1. フレームワーク: Next.js
  2. アプリのデプロイ: Vercel
  3. サーバーレスデータベース: Neon
  4. ORM: Prisma

1. Vercel MarketplaceでNeonのデータベースを生成して接続する

1章では、ブラウザからVercelにアクセスして、データベースの設定を行っていきます。

1.1. Vercel MarketplaceでNeonのデータベースを生成する

まずは、Vercelのプロジェクトを開き、StorageタブからNeonをCreateします。この記事では、Vercelのプロジェクトが既に作成されている状態から、Storageを追加する手順の紹介しています。

Create New Neon Accountのダイアログが表示されるため、Accept and Createをクリックします。

次に、データベースの設定を行います。筆者は全て初期値のままFreeプランでContinueをクリックしました。

Database NameはVercelのプロジェクト名と同じdiceとしたかったのですが、5文字以上の入力が必要であったため、dice-z19iとしました。Createをクリックするとデータベースが生成されます。

これでNeonのデータベースの生成は完了です。Doneをクリックしてダイアログを閉じます。

1.2. VercelのプロジェクトにNeonのデータベースを接続する

次に、Vercelのプロジェクトへのデータベースの接続を行います。ここではdiceというVercelのプロジェクトに、先ほど作成したdice-z19iというデータベースを接続しています。Connectをクリックすると接続が行われます。

2. Prismaを設定してデータベースにテーブルを生成する

2章では、Next.jsのプロジェクトにPrismaを導入していきます。筆者は、Vercelの以下のドキュメントを参考に進めました。

https://vercel.com/guides/nextjs-prisma-postgres

2.1. Prismaをインストールする

以下のコマンドを実行して、prismaをインストールします。

> npm install prisma --save-dev

2.2. ローカル環境の環境変数を設定する

VercelのプロジェクトのStorageタブから、1章で作成したデータベースを選択し、Quickstartの.env.localタブをコピーして、Next.jsのプロジェクトのルートに.env.localを作成します。

/.env.local
# Recommended for most uses
DATABASE_URL=************

# For uses requiring a connection without pgbouncer
DATABASE_URL_UNPOOLED=*********************

# Parameters for constructing your own connection string
PGHOST=******
PGHOST_UNPOOLED=***************
PGUSER=******
PGDATABASE=**********
PGPASSWORD=**********

# Parameters for Vercel Postgres Templates
POSTGRES_URL=************
POSTGRES_URL_NON_POOLING=************************
POSTGRES_USER=*************
POSTGRES_HOST=*************
POSTGRES_PASSWORD=*****************
POSTGRES_DATABASE=*****************
POSTGRES_URL_NO_SSL=*******************
POSTGRES_PRISMA_URL=*******************

本番環境の環境変数については、1章のデータベースの生成と接続を行うことで、既に設定されています。VercelのSettingsタブのEnviroment Variablesから、設定された値を確認することができます。

2.3. Prismaのスキーマを定義する

Next.jsのプロジェクトに/prisma/schema.prismaを作成し、Prismaのスキーマを定義します。筆者のスキーマ定義は以下の通りです。ゲームのモードとスコアを定義しています。

/prisma/schema.prisma
// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider  = "postgresql"
  url  	    = env("DATABASE_URL")
}

model Score {
  id        Int      @id @default(autoincrement())
  score     Int
  mode      String
  createdAt DateTime @default(now())
}

2.4. データベースにテーブルを生成する

以下のコマンドを実行して、データベースにテーブルを生成します。

> npx prisma db push

コマンドを実行すると、以下のようなアウトプットが得られます。

Environment variables loaded from .env.local
Prisma schema loaded from prisma\schema.prisma
Datasource "db": PostgreSQL database "neondb", schema "public" at "********"

Your database is now in sync with your Prisma schema. Done in 5.76s

2.5. Prisma Clientをインストールして生成する

まず、以下のコマンドを実行して、Prisma Clientをインストールします。

> npm install @prisma/client

次に、以下のコマンドを実行して、Prisma Clientを生成します。

>npx prisma generate

最後に、Next.jsのプロジェクトに、/lib/prisma.tsを作成して以下の実装を行います。このファイルを任意のファイルからインポートすることで、どのファイルからでも単一のPrismaClientインスタンスを使用することができるようになります。

/lib/prisma.ts
import { PrismaClient } from "@prisma/client";

declare global {
  // eslint-disable-next-line no-var
  var prisma: PrismaClient | undefined;
}

let prisma: PrismaClient;

if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient();
} else {
  if (!global.prisma) {
    global.prisma = new PrismaClient();
  }
  prisma = global.prisma;
}

export default prisma;

2.6. Vercelのビルド依存関係のキャッシュを回避する

Vercelのビルド依存関係のキャッシュを回避するために、Vercelのプロジェクトのpackage.jsonのscriptsに以下のスクリプトを追加してください。

package.json
  "scripts": {
    "postinstall": "prisma generate",
  },

この問題の詳細は、Prismaの以下の記事に記載されています。
https://www.prisma.io/docs/orm/more/help-and-troubleshooting/vercel-caching-issue

3. データベースにアクセスするためのAPIを実装する

3章では、作成したデータベースにアクセスするためのAPIを実装します。ここでは、値の取得のためのGETメソッドと、値の追加のためのPOSTメソッドを実装しています。

/app/api/score/route.ts
import prisma from "../../../lib/prisma";
import { ScoreSchema } from "@/schema/schema";

export async function GET() {
  try {
    const scores = await prisma.score.findMany({
      orderBy: { score: "desc" },
    });

    return new Response(JSON.stringify(scores), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: "Internal Server Error." }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const parsedBody = ScoreSchema.safeParse(body);
    if (!parsedBody.success) {
      return new Response(JSON.stringify({ error: "Bad Request." }), {
        status: 400,
        headers: { "Content-Type": "application/json" },
      });
    }

    const score = await prisma.score.create({
      data: parsedBody.data ,
    });

    return new Response(JSON.stringify(score), {
      status: 201,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: "Internal Server Error." }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

以下はPOSTでRequestのパースに使っているZodのスキーマ定義です。

/schema/schema.ts
export const ScoreSchema = z.object({
  mode: z.enum(["NORMAL", "ONE_SHOT"]),
  score: z.number().min(0),
});

お疲れ様でした。ここで実装したGETとPUTのAPIを呼び出すことで、データベースの読み書きを行うことができます。

/app/page.tsx
  const postScore = async () => {
    try {
      const response = await fetch("/api/score", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({score, mode}),
      });
      if (!response.ok) {
        throw new Error("Failed to post score.");
      }
    } catch (error) {
      console.error(error);
    }
  };

筆者のWebアプリ

記事を読んでいただいてありがとうございました。今回はNext.jsにVercel MarketplaceでNeonのPostgresを導入する方法を紹介しました。この記事の方法で、NeonのPostgresから世界最高得点の読み書きを行っている筆者のアプリは以下です。
以下のリンクから遊べますので、是非世界最高得点の更新を目指してみてください!

https://dice-z19i.vercel.app/

Discussion