この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
西田@MADグループです
今回はフルマネージドなサーバーレスRDBである、 PlanetScale を Next.js で作成したアプリケーションから使ってみたいと思います
なお、この記事で作成するアプリケーションのソースコードはこちらのリポジトリにアップロードしてます
PlanetScale とは?
PlnetScale は MySQL互換のサーバーレスデーターベースです
シームレスに水平スケーリングでき、その裏側として Vitess が採用されています
Vitess は Youtube や Slack でも採用された実績があるオープンソースデータベースです
PlanetScale の主な特徴
- 設定なしで水平スケールが可能で、大規模なデータやトラフィックに対応
- ダウンタイムなしのテーブル定義変更
- Git のようなブランチ機能
- サーバーレス
PlanetScale にサインアップ
ます PlanetScale にアカウントを以下のページから作成します
https://auth.planetscale.com/sign-up
※ 執筆時点では Github と Email でのサインアップが可能でした
今回の構成
- Next.js
- Prisma
- PlanetScale
データベースを作成
データベースを作成します
任意の名前を入力して、リージョンを選択します
リージョンには ap-northeast-1 (東京) を選択しました
Next.js のセットアップ
Next.js のプロジェクトを作成します
今回は TypeScript で作成し、 src 配下に pages と styles を配置します
$ npx create-next-app --typescript sample
$ cd sample
$ mkdir src
$ mv pages styles src/
この時点で一度動作確認をしておきます
npm run dev
もしくは yarn dev
をターミナルで実行して、ブラウザで http://localhost:3000 にアクセスします
$ npm run dev
Prisma をセットアップ
今回はORMとして Prisma を使用します
npx prisma init
で必要なファイルを生成し、Prisma を使って PlanetScale に接続するために必要な設定をします
prisma init
コマンドで必要なファイルを生成
$ npx prisma init
prisma init
コマンドで生成された prisma/schema.prisma
ファイルを編集
generator client {
provider = "prisma-client-js"
// 以下の一文を追加
previewFeatures = ["referentialIntegrity"]
}
datasource db {
// provider に mysql を指定
provider = "mysql"
url = env("DATABASE_URL")
// 以下の一文を追加
referentialIntegrity = "prisma"
}
referentialIntegrity を有効にする理由
Prisma ではMongoDB 以外のコネクタの場合、デフォルトで外部キー制約を使って、参照整合性を担保します
ただし、 PlanetScale では外部キー制約をサポートしません
Prisma で PlanetScale を使用する際は、referentialIntegrity を “prisma” にし外部キー制約を使用しないようにします
参考: Setting the referential integrity
PlanetScale CLI をインストール
PlanetScale をCLIで操作するためのコマンドを pscale
をインストールします
$ brew install planetscale/tap/pscale
$ brew install mysql-client
※ 上記は Mac でのインストール方法です。その他のOSは以下を参考にしてください
参考: PlanetScale CLI Installation
PlanetScale にサインインしてCLIを利用する準備をします
$ pscale auth login
pscale auth login
コマンドを実行するとブラウザが起動し Confirmation Code を確認するページに遷移するので、ターミナルに表示された、 Confirmation Code と一致してるか確認して「Confirm code」をクリックします
※ mysql-client がまだ未インストールの場合
pscale コマンドでクエリを実行するのに mysql-client が必要なので、まだインストールしていない場合はインストールします
$ brew install mysql
※ 上記は Mac でのインストール方法です。
PlanetScale への接続をプロキシ
PlanetScale CLI を使って、ローカルマシンの 3309ポート(任意) に PlanetSclae への接続をプロキシし、ローカルマシンの 3309番ポートから PlanetScale に接続できるようにします
$ pscale connect $DB_NAME main --port 3309
$DB_NAME にはデータベースを作成時に設定した Name (この記事なら test-branch)を指定します
PlanetScale の接続情報を設定
.env
ファイルにPlanetScaleへの接続文字列を記述し、環境変数として参照できるようにします
DATABASE_URL="mysql://root@127.0.0.1:3309/$DB_NAME"
Prisma のスキーマを定義
prisma/schema.prisma
ファイルにスキーマを追記し、Inquiry モデルを定義します
model Inquiry {
id Int @default(autoincrement()) @id
name String
email String
subject String
message String
}
Prisma のスキーマを PlanetScale に反映
Prisma のスキーマを PlanetScale に反映します
この操作でデータベースとテーブルが作成されます
$ npx prisma db push
スキーマの反映を確認
psclae shell
コマンドでPlanetScale上で動作中のデータベースにアクセスし、スキーマが反映されたのを確認します
$ pscale shell $DB_NAME main
$DB_NAME/main> show tables;
+-----------------------+
| Tables_in_test-branch |
+-----------------------+
| Inquiry |
+-----------------------+
$DB_NAME/main> desc Inquiry;
+---------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(191) | NO | | NULL | |
| email | varchar(191) | NO | | NULL | |
| subject | varchar(191) | NO | | NULL | |
| message | varchar(191) | NO | | NULL | |
+---------+--------------+------+-----+---------+----------------+
Next.js の API Route でデータを登録するAPIを作成
プロジェクト内のファイルをインポートするときに、プロジェクトのルートディレクトリからの絶対パスでインポートできるよう tsconfig.json
を変更します
{
"compilerOptions": {
// 省略...
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
src/libs/database.ts
ファイルを作成し、データベースにデータを登録する関数を作成します
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient()
export type InquiryInput = {
name: string,
email: string,
subject: string,
message: string,
}
export async function createInquiry(params: InquiryInput) {
return await prisma.inquiry.create({data: params});
}
src/pages/api/inquiries/index.ts
ファイルを作成し Inquiry を登録するAPI(POST /api/inquiries) を作成します
import { createInquiry } from "@/libs/database";
import type { NextApiRequest, NextApiResponse } from "next";
const postInquiry = async (req: NextApiRequest, res: NextApiResponse) => {
const body = req.body
const inquiry = await createInquiry({
name: body.name,
email: body.email,
subject: body.subject,
message: body.message,
})
return res.status(200).json(inquiry);
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
switch(req.method) {
case "POST":
return await postInquiry(req, res);
default:
return res.status(405).json({message: "Method not allowed"})
}
}
参考: Next.js API Route (TypeScript)
curl コマンドを使って動作確認をします
$ curl -XPOST -H "content-type: application/json" localhost:3000/api/inquiries -d "{\"name\": \"abc\", \"email\":\"abc@example.com\",\"subject\": \"sample\",\"message\":\"sample message\"}"
> {"id":1,"name":"abc","email":"abc@example.com","subject":"sample","message":"sample message"}⏎
Next.js の API Route でデータを取得するAPIを作成
src/libs/databse.ts
にデータベースからデータを取得する関数を追加します
export async function fetchInquiries() {
return await prisma.inquiry.findMany();
}
src/pages/api/inquiries/index.ts
ファイルに Inquiry を取得するAPI (GET /api/inquiries) を追加します
export default async (req: NextApiRequest, res: NextApiResponse) => {
switch(req.method) {
case "POST":
return await postInquiry(req, res);
case "GET":
return await getInquiries(req, res);
default:
return res.status(405).json({message: "Method not allowed"})
}
}
作成したAPIを使って画面表示します
今回は Tailwind CSS を使用してスタイルを設定するので、その準備をします
$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
tailwind.config.js
を編集
module.exports = {
mode: "jit",
content: [
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
続いてデータ取得ライブラリの SWR をインストールします
$ npm install swr
src/pages/index.tsx
を以下のように変更します
主に以下の処理をしています
- 先ほど作成したデータ取得 API を呼び出し
- 取得したデータを使用してテーブルのデータ行部分をレンダリング
import type { NextPage } from 'next'
import useSWR from 'swr'
import { Inquiry } from '@prisma/client'
type Inquiries = Array<Inquiry>
const fetcher = (url: string) => fetch(url).then(res => res.json())
const Home: NextPage = () => {
const {data, error} = useSWR<Inquiries>("/api/inquiries", fetcher);
if (error) return <div>Error has oocurred</div>
if (!data) return <div>Now loading...</div>
return (
<div className="container mx-auto">
<table className="w-full text-sm text-left text-gray-500 mt-3">
<thead className="text-xs text-gray-700 bg-gray-50">
<tr>
<th className="py-3 px-6">Name</th>
<th className="py-3 px-6">Email</th>
<th className="py-3 px-6">Subject</th>
<th className="py-3 px-6">Message</th>
</tr>
</thead>
<tbody>
{data.map((inquiry) => {
return (
<tr className="bg-white border-b" key={inquiry.id}>
<td className="py-4 px-6">{inquiry.name}</td>
<td className="py-4 px-6">{inquiry.email}</td>
<td className="py-4 px-6">{inquiry.subject}</td>
<td className="py-4 px-6">{inquiry.message}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}
export default Home
作成したAPIを使って画面から登録できるようにします
以下のコードを src/pages/index.tsx
に書き加えます。
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [subject, setSubject] = useState("");
const [message, setMessage] = useState("");
const clearForm = () => {
setName("");
setEmail("");
setSubject("");
setMessage("");
}
const {data, error, mutate} = useSWR<Inquiries>("/api/inquiries", fetcher);
const onSubmit = async (e: React.SyntheticEvent) => {
e.preventDefault();
const response = await fetch("/api/inquiries", {method: "POST", body: JSON.stringify({
name, email, subject, message
}),
headers: {
"Content-Type": "application/json"
}})
clearForm();
mutate();
}
SWRはローカルにキャッシュを持つので、更新処理後に即座に画面に反映させるには、mutate 関数を使って、SWR のローカルキャッシュをクリアし、再度データを取得させる必要があります
const {data, error, mutate} = useSWR<Inquiries>("/api/inquiries", fetcher);
mutate();
src/pages/index.tsx
に以下をフォームを書き加えます
<form action="" className="bg-white rounded px-8 shadow-md mt-3 p-3" onSubmit={onSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-1">
Name
</label>
<input onChange={(e) => setName(e.target.value)} type="text" value={name} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-1">
Email
</label>
<input onChange={(e) => setEmail(e.target.value)} type="text" value={email} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-1">
Subject
</label>
<input onChange={(e) => setSubject(e.target.value)} type="text" value={subject} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-1">
Message
</label>
<textarea onChange={(e) => setMessage(e.target.value)} value={message} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700" />
</div>
<div className="flex items-center justify-between">
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Post</button>
</div>
</form>
アプリケーションを Vercel にデプロイ
最後に今回作成したアプリケーションを Vercel で動かしてみます
PlanetScale でデータベース接続情報を取得
PlanetScale でブランチを選択し、「Connect」ボタンを押します
「New Password」 をクリックしデータベースへ接続するためのパスワードを生成し、 「Connect With」に Prisma
を選択して、データベース接続文字列を表示させます
コードを Github に Push
Vercel にデプロイできるようにするため、今回作成したコードを Github に Push します
$ git add -A
$ git commit -m "initial commit"
$ git push origin main
Vercel にアカウントを作成
Vercel にアカウントを作成します。
Vercel にデプロイするリポジトリをインポート
今回は Github のリポジトリをデプロイするため、Githubからリポジトリをインポートします
今回作成したリポジトリを選択します
プロジェクト設定の Environment Variables に DATABSE_URL
という名前で、先ほど PlanetScale で表示したデータベース接続情報文字列 mysql://
で始まる部分を抜き出し設定します
そのあと「Deploy」をクリックします
デプロイしたプロジェクトのドメインにアクセスし
画面が見えれば完了です