Next.js + Neon + Prismaでユーザー登録を実装!サーバーレスデータベースを活用した簡単な認証システム

2024/02/22に公開

はじめに

前回まではサーバーアクションと連携し入力フォームの入力内容を渡す処理を作成しました。
今回は受け取った入力内容をもとにサーバーアクションでデータベースへの登録処理を実装します。
使用するデータベースの紹介、データベースにアクセスするためのライブラリの紹介などしつつ、登録までの流れをご説明いたします。
前回の記事ではサーバーアクションが100%エラーを返す状態で終わらせてしまいましたが、こちらもデータベースへの登録後に成功したメッセージを返すよう修正していきます。

https://shinagawa-web.com/blogs/nextjs-server-actions-form-data

今回のゴール

実施にユーザー登録をアカウント登録画面から行います。下記のように「アカウントを登録しました」と表示されるところまで実装していきます。
また、登録したユーザーを使ってログインする処理は少し先の記事となるため、今回は登録できたことを確認するために、データベースに直接アクセスする手順もご紹介します。

Image from Gyazo

データベースNeonについて

NeonはPostgresをサーバーレスで提供しているサービスとなります。

https://neon.tech/

サーバーレスでデータベースを提供する企業では他にも、MySQLベースのPlanetScaleやPostgresベースのSupabaseなどが有名です。それらに比べると比較的新しいNeonで今回は実装してみます。

https://planetscale.com/

https://supabase.com/

Neonは2024年9月現在、1アカウントにつき1データベースを無料で提供しています。そのため、検証用としても始めやすくなっています。こちらでご紹介したPlanetScaleも以前は1データベースを無料で提供していたのですが、最近有料に切り替わりました。そういう意味ではNeonも今後、無料から有料に切り替えるかもしれません。

ORM(Object-Relational Mapping)ツールPrismaについて

Next.jsのサーバーアクションからデータベースへの接続はPrismaを使っていきます。

https://www.prisma.io/

Prisma自体様々な製品を提供しておりますがその中で今回使用する2つの製品をご紹介します。

  • ORM: NodeやTypeScriptベースのバックエンドサービスで使用可能
  • Studio: ローカルからデータベースをブラウザで参照

対応しているデータベースやフレームワークの一覧はこちらのページにまとまっております。

https://www.prisma.io/stack

Neonだけでなく、先ほどご紹介したデータベースであるPlanetScaleSupabaseも対応しています。そのため今回はNeonをデータベースとして実装を進めていきますが、Prismaを使うのであれば、PlanetScaleSupabaseでも大体同じような実装が可能となります。

また、PrismaTypeScriptと密接に連携しており、データベーススキーマに基づいて自動生成された型定義を使うことで、型安全なクエリが可能です。これにより、クエリの構築時にタイプミスや構文エラーを事前に検出でき、開発者の生産性が向上します。

読みやすいデータモデル Prismaのスキーマは直感的で、JavaScriptやTypeScriptを普段使用している人が読める方法でデータベースのテーブルを宣言できます。 手作業でモデルを定義したり、既存のデータベースの構造(テーブルやカラムの情報)を自動的に読み取って、Prismaスキーマを生成する

Image from Gyazo

引用元:https://www.prisma.io/orm

VS CodeのPrisma拡張機能と一緒に使うことでデータを取得する際に使用可能なAPIを候補として出してくれる自動補完などもあるため、実装しやすくなっております。

Image from Gyazo

引用元:https://www.prisma.io/orm

データベースの準備

まずはNeonでアカウントを登録します。
こちらのページでサインアップをします。

https://neon.tech/

無料で作成できるデータベースの名前とプロジェクト名とリージョンを指定します。

Image from Gyazo

以上でデータベースの作成は終了です。
次に作成したデータベースにアクセスするための情報を確認するため、今回使用するPrismaを選択して、.envを参照します。
DATABASE_URL=で始まる記述がありますので、これをプロジェクトのトップに.envファイルを作成した上で貼り付けます。

Image from Gyazo

Prismaの準備

次にPrismaを使えるよう準備していきます。
基本的な流れはPrisma公式にもありますのでこちらを参考にしつつ進めていきます。

https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/introduction

スキーマ定義を生成するライブラリのインストール

下記のコマンドを実行します。

$ npm install prisma --save-dev
$ npx prisma

コマンド使用例が紹介されていればOKです。

    ◭  Prisma is a modern DB toolkit to query, migrate and model your database (https://prisma.io)

    Usage

      $ prisma [command]

    Commands

                init   Set up Prisma for your app
            generate   Generate artifacts (e.g. Prisma Client)
                  db   Manage your database schema and lifecycle
             migrate   Migrate your database
              studio   Browse your data with Prisma Studio
            validate   Validate your Prisma schema
              format   Format your Prisma schema
             version   Displays Prisma version info
               debug   Displays Prisma debug info

(※以下省略)

スキーマ定義の作成

先ほどのコマンド使用例で記載のあるinitでPrismaをセットアップします。

$ npx prisma init
$ npx prisma init


✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.
5. Tip: Explore how you can extend the ORM with scalable connection pooling, global caching, and real-time database events. Read: https://pris.ly/cli/beyond-orm

More information in our documentation:
https://pris.ly/d/getting-started

すると、prismaフォルダ配下にschema.prismaファイルが生成され、下記のような記述があるかと思います。

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

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

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

このファイルに早速モデルを書いてみます。

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

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

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

+ model User {
+   id       String @id @default(cuid())
+   name     String
+   email    String @unique
+   password String
+ }

Userというモデル名で作成します。idは自動で採番するよう定義し、emailはユニークであるよう定義しています。

スキーマ定義から型の生成

TypeScriptで使用するための型を下記コマンドで生成します。

$ npx prisma generate
$ npx prisma generate
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma

✔ Generated Prisma Client (v5.18.0) to ./node_modules/@prisma/client in 73ms

Start by importing your Prisma Client (See: http://pris.ly/d/importing-client)

Tip: Need your database queries to be 1000x faster? Accelerate offers you that and more: https://pris.ly/tip-2-accelerate

型を使用しつつデータ取得処理を書いていくのですが、Prismaクライアントのインストールを行なってからとなりますので、今はこのままでOKです。

スキーマ定義をデータベースに反映

データベースにスキーマ定義を反映させることでUserというテーブルが作成されます。

$ npx prisma db push

Prisma Studioでデータベースを確認

データベースにテーブルができたことを確認します。

$ npx prisma studio

Image from Gyazo

モデル名にUserがあるのでクリック

Image from Gyazo

スキーマ定義で設定したidとかnameがあることが確認できます。

Next.jsからデータベースにアクセスするPrismaクライアントのインストール

下記のコマンドを実行します。

$ npm install @prisma/client

Next.jsでPrismaクライアントを使う際に必要な設定

ローカル環境で開発していると下記のようなメッセージが出ることがあります。

warn(prisma-client) There are already 10 instances of Prisma Client actively running.

npm run devで開発している場合、コードを書き換えるたびにNext.jsが自動で読み取って最新の状態でブラウザに表示されます。このタイミングでデータベースのコネクションも都度新しく作られてしまうため、上記のメッセージが表示されます。

ローカルで開発する際のためにlibフォルダ配下に、下記のファイルを定義します。

db.ts
import { PrismaClient } from '@prisma/client'

const prismaClientSingleton = () => {
  return new PrismaClient()
}

declare const globalThis: {
  prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;

const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()

export default prisma

if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma

このファイルで定義したprismaを使ってデータベースにアクセスしていきます。

Next.jsからデータベースにアクセスしユーザーの登録をする

ようやく長いセットアップが終わりました。
ここからサーバーアクションにコードを書いてデータベースに登録する作業を進めていきます。

パスワードのハッシュ化

今回の認証方式ではパスワードをデータベースに保存し、ログインする際にはパスワードを受け取って、保存されているパスワードと一致していることを確認しアクセス許可をします。
実装としてはシンプルなため初めて認証の仕組みを作るにはお勧めですがパスワードの管理が重要となります。
今回はパスワードをハッシュ化した上で保存します。
その後、ログイン時にはログイン画面でパスワードをユーザーが入力するため、そのパスワードをハッシュ化した上で、ハッシュ化されたデータベース上のパスワードと一致するか確認するという手段を取ります。

任意の文字列をハッシュ化するライブラリをインストールします。

$ npm install bcryptjs
$ npm install -save-dev @types/bcryptjs

サーバーアクションの修正

サーバアクションを下記の通りに書き換えます。(修正箇所多いのでそのままコピペしていただくのがいいかと思います。)

register.ts
'use server'

import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import bcryptjs from 'bcryptjs'
import type { z } from 'zod'
import db from '@/lib/db'
import { RegisterSchema } from '@/schema'

export const register = async (values: z.infer<typeof RegisterSchema>) => {
  const validatedFields = RegisterSchema.safeParse(values)

  if (!validatedFields.success) {
    return { error: '入力内容を修正してください' }
  }

  const { name, email, password } = validatedFields.data
  const hashedPassword = await bcryptjs.hash(password, 10)

  try {
    await db.user.create({
      data: {
        name,
        email,
        password: hashedPassword,
      },
    })

    return { success: 'アカウントを登録しました' }
  } catch (e) {
    if (e instanceof PrismaClientKnownRequestError) {
      if (e.code === 'P2002') {
        return { error: 'このメールアドレスは既に登録されています' }
      }
    }
    console.log(e)
    return { error: 'エラーが発生しました' }
  }
}

コードの解説をしていきます。

import bcryptjs from 'bcryptjs'

(※一部省略)

  const { name, email, password } = validatedFields.data
  const hashedPassword = await bcrypt.hash(password, 10)

Zodを使って受け取ったデータのスキーマチェックが通った後に、必要な値を取り出します。
その中からパスワードに関してはbcryptjsを使ってハッシュ化されたパスワードを取得します。

import db from '@/lib/db'

(※一部省略)

  try {
    await db.user.create({
      data: {
        name,
        email,
        password: hashedPassword,
      },
    })

    return { success: 'アカウントを登録しました' }
  }

実際にデータベースに登録する処理です。名前とメールアドレスとハッシュ化されたパスワードで登録します。VS Codeで実際にコードを書くとき、db.とすると使用できるAPIやモデルが候補として出てくるため非常に便利です。

Image from Gyazo

データベース登録でエラーが発生しなければ、入力画面に対して「アカウントを登録しました」のメッセージを返します。

import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'

catch (e) {
    if (e instanceof PrismaClientKnownRequestError) {
      if (e.code === 'P2002') {
        return { error: 'このメールアドレスは既に登録されています' }
      }
    }
    console.log(e)
    return { error: 'エラーが発生しました' }
  }

データベース登録でエラーが発生した場合の処理となります。先ほどモデルで定義したようにメールアドレスはユニークである必要がありますので、万が一、同じメールアドレスで登録されそうになった場合は「このメールアドレスは既に登録されています」というエラーを返すようにしています。

データベースに関するエラーは多岐に渡り他にもユーザーに通知した方がいいエラーなどあるかもしれません。下記のドキュメントにはエラーコードとエラー内容がまとまっていますのでこちらから必要なエラーを見つけて、条件分岐に追加してもいいかと思います。

https://www.prisma.io/docs/orm/reference/error-reference#error-codes

今回は上記のエラー以外は全て「エラーが発生しました」とだけ返すようにしました。

入力フォームの修正

サーバーアクションで成功した時のメッセージを返せるように修正したので、アカウント登録画面の入力フォームでは成功した時のメッセージを表示できるよう修正します。

register-form.tsx
(※一部省略)


  const onSubmit = async (values: z.infer<typeof RegisterSchema>) => {
    setError('')
    setSuccess('')
    setTransition(async () => {
      try {
        const response = await register(values)
        if (response.error) {
          setError(response.error)
        } else {
-           // setSuccess(response.success)
+           setSuccess(response.success)
        }
      } catch (e) {
        setError('エラーが発生しました')
      }
    })
  }

(※一部省略)

動作確認

実施にユーザー登録をアカウント登録画面から行います。
下記のように「アカウントを登録しました」と表示されればOKです。

Image from Gyazo

Prisma Studioでは登録された内容が確認できます。

$ npx prisma studio

Image from Gyazo

名前、メールアドレスが入力内容通りに登録されており、パスワードは入力内容とは違う文字列が入っていることが確認できます。

試しに先ほど使用したメールアドレスを使って再度「アカウント作成」を行います。

Image from Gyazo

「このメールアドレスは既に登録されています」のメッセージが表示されれば想定通りの挙動となります。

さいごに

今回はデータベースとORMツールのセットアップを行なった上でNext.jsのサーバーアクションからデータベースへの登録処理を行いました。また、Prisma Studioという便利なツールを使いつつデータベースの内容を見れるようにしました。
次回以降は登録した情報を今度は取得できるようにしつつ、ログインができるように実装して行きます。

次の記事はこちら

https://shinagawa-web.com/blogs/nextjs-middleware-auth-access-control

記事に関するお問い合わせ📝

記事の内容に関するご質問、ご意見などは、下記よりお気軽にお問い合わせください。
ご質問フォームへ

技術支援などお仕事に関するお問い合わせ📄

技術支援やお仕事のご依頼に関するお問い合わせは、下記よりお気軽にお問い合わせください。
お問い合わせフォームへ