Next.js / Express / Prisma / Docker Compose でプロダクトを構築するフロー
事前準備
Next.js を使うので、 create-next-app
を入れておく。
$ npm install -g create-next-app@latest
フロントエンドの環境構築
今回は Next.js をインストールしていく。
$ cd packages
$ npx create-next-app frontend
✔ Would you like to use TypeScript with this project? … No / Yes
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Use App Router (recommended)? … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
Creating a new Next.js app in $HOME/Project/hogehoge/packages/frontend.
Using npm.
Initializing project with template: app-tw
Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- tailwindcss
- postcss
- autoprefixer
- eslint
- eslint-config-next
added 352 packages, and audited 353 packages in 17s
136 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Success! Created frontend at $HOME/Project/hogehoge/packages/frontend
データベース環境の構築
次にバックエンドを構築していく。その前に、バックエンドで呼び出すデータベースを構築する。
プロジェクトルートにおいてある docker-compose.yml
を編集する。
今回のプロダクトは AWS での運用を想定しているので、イメージに AWS のパブリックイメージを使う。
version: '3.9'
services:
db:
container_name: db
image: public.ecr.aws/ubuntu/mysql:latest
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: dev
MYSQL_PASSWORD: password
restart: always
volumes:
- db:/var/lib/mysql
networks:
- db-network
networks:
db-network:
driver: bridge
volumes:
db:
Prisma2 の導入
データベースを作ったら次はそれに接続するための環境を構築していく。今回は Prisma2 を利用することにする。
$ cd packages/backend
$ npx prisma init
✔ Your Prisma schema was created at prisma/schema.prisma
You can now open it in your favorite editor.
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.
More information in our documentation:
https://pris.ly/d/getting-started
そのあとは、 Next steps
に書いてある通りに進める。
まずは .env
が packages/backend
の中に作ってあるので、そこの DATABASE_URL
を変更する。
デフォルトでは PostgreSQL で書いてあるので、 MySQL のケースに書き直す。
ここを参考にすると良いかもしれない。
DATABASE_URL="mysql://{ユーザー名}:{パスワード}@localhost:{ポート番号}/{データベース名}"
ここまでで「接続するだけなら」できるようになっているので、接続できるかを確認するために簡単なマイグレーションを書いてみる。
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Todo {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
completed Boolean @default(false)
}
今回は Todo というモデルを作って、テーブルを作成する。データベースにテーブル作成するために、以下のコマンドを使う。
$ npx prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": MySQL database "db" at "127.0.0.1:3306"
MySQL database db created at 127.0.0.1:3306
Applying migration `20230606072246_init`
The following migration(s) have been created and applied from new schema changes:
migrations/
└─ 20230606072246_init/
└─ migration.sql
Your database is now in sync with your schema.
✔ Generated Prisma Client (4.14.1 | library) to ./../../../../node_modules/@prisma/client in 34ms
init
という名前でマイグレーションを作成している。
これによって、 packages/backend/prisma
の中に migrations
というディレクトリが作成される。中身はこんな感じ。
-- CreateTable
CREATE TABLE `Todo` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
`title` VARCHAR(191) NOT NULL,
`completed` BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
これで、データベースに Todo テーブルが作成された。
シードデータを登録する
開発環境なので、シードデータが欲しい。
ここを参考にして作っていく。
まずは TypeScript と ts-node をインストールする。これは、シードデータのファイルを TypeScript で作るため、トランスパイルして実行してくれる環境は必要になるから。
同時に、クライアントになるライブラリもインストールしておく。
$ npm init -y
Wrote to $HOME/Project/hogehoge/packages/backend/package.json:
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
$ npm install -D typescript ts-node @types/node
added 19 packages, and audited 20 packages in 799ms
found 0 vulnerabilities
$ npm install -D prisma
added 2 packages, and audited 22 packages in 3s
found 0 vulnerabilities
$ npm install @prisma/client
added 2 packages, and audited 24 packages in 3s
found 0 vulnerabilities
そして、シードデータを保存するためのコードを書いていく。
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const task1 = await prisma.todo.upsert({
where: { id: 1 },
update: {},
create: {
id: 1,
title: "Task 1",
},
});
const task2 = await prisma.todo.upsert({
where: { id: 2 },
update: {},
create: {
id: 2,
title: "Task 2",
},
});
console.log({ task1, task2 });
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
prisma.todo
は、 schema.prisma
で定義した Todo
が適用される。そして upsert
では、 where
句の条件でもし見つかった場合にはアップデートが走り、見つからなかった場合はインサートが走るという書き方ができる。
これを実行するために、必要なことを package.json
に書き込んでいく。
{
// ...
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"commonjs\"} ./prisma/seed.ts"
},
// ...
}
ここで npx prisma db seed
とすると、実はまだ足りなくて、 @prisma/client が初期化されていない、というエラーに遭遇する。ので、実行しておく。
$ npx prisma generate
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
✔ Generated Prisma Client (4.15.0 | library) to ./node_modules/@prisma/client in 54ms
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client
\`\`\`
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
\`\`\`
これでようやくシードデータを流し込む準備が整ったので、実行してみる。
$ npx prisma db seed
Environment variables loaded from .env
Running seed command `ts-node --compiler-options {"module":"commonjs"} ./prisma/seeds.ts` ...
{
task1: {
id: 1,
createdAt: 2023-06-06T07:53:11.337Z,
updatedAt: 2023-06-06T07:53:11.337Z,
title: 'Task 1',
completed: false
},
task2: {
id: 2,
createdAt: 2023-06-06T07:53:11.358Z,
updatedAt: 2023-06-06T07:53:11.358Z,
title: 'Task 2',
completed: false
}
}
🌱 The seed command has been executed.
成功。
Prisma Studio の導入
実際に保存できているかを確認するために、 Prisma Studio を導入する。
これは Prisma 経由でデータをGUIで確認できるツールで、公式から提供されている。
とは言っても難しいことは一切なく、単に以下のコマンドを実行するだけで良い。
$ npx prisma studio
こうするだけで、ブラウザで以下のような画面が確認できるようになる。
デフォルトでは URL は http://localhost:5555
となるが、ポート番号はオプションで差し替えることも可能。詳細はこちら。
Express で Hello World する
データベースにアクセスできるようになったので、今度は Express を使って API サーバーを構築していく。
まずは Express 自体のインストールを行う。
$ npm install express
added 58 packages, and audited 82 packages in 897ms
8 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
$ npm install -D @types/express
added 9 packages, and audited 91 packages in 2s
8 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
これで準備が完了したので、実装を進める。
まずは開発用なので、 Hello World
から作っていく。
import express from 'express';
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(PORT, () => {
console.log(`Running the app on port: ${PORT}`);
});
そして、これを実行するためのコマンドを作っていく。
{
// ...
"scripts": {
"dev": "ts-node ./src/index.ts"
},
// ...
}
最後に、実行してみる。
$ npm run dev
> backend@1.0.0 dev
> ts-node ./src/index.ts
Running the app on port: 3000
するとこういう感じで、 http://localhost:3000
にアクセスできるようになる。
Express で API サーバーを構築する
Hello できたので、次は Prisma でデータを取得できるようにする。先ほどのファイルに、以下のように追加していく。
import express from 'express';
import { PrismaClient } from '@prisma/client';
const app = express();
const prisma = new PrismaClient();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.get('/api/todos', async (req, res) => {
const result = await prisma.todo.findMany();
res.json(result);
});
app.listen(PORT, () => {
console.log(`Running the app on port: ${PORT}`);
});
これで npm run dev
を再起動して、ブラウザから http://localhost:3000/api/todos
にアクセスすると、データベースに保存してある Todo を JSON 形式で取得できる。
Express をホットリロードする
このままだと、何か更新するたびにコマンドを打ち直す必要があって、全く実用的ではない。なので、ファイルの変更を監視してホットリロードする仕組みを入れていく。[1]
まずは nodemon
をインストールする。
$ npm install -D nodemon
そして、設定ファイルを作っていく。今回は「 src
ディレクトリ内に存在するファイルを監視する」「 ts
拡張子のファイルを監視する」という条件を設定する。
{
"watch": ["./src"],
"ext": "ts",
"exec": "npm run dev",
}
それに合わせて、 package.json の scripts も変更していく。
{
// ...
"scripts": {
"watch": "npx nodemon",
"dev": "ts-node src/index.ts",
},
// ...
}
"exec" というオプションで、これまで実行していた npm run dev
を動かして、通常は npm run watch
を実行することで、ファイル監視を行いながらホットリロードを実現する。
この状態でファイルを変更すると、以下のようなコンソールログが表示される。
$ npm run watch
> backend@1.0.0 watch
> npx nodemon
[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: ts
[nodemon] starting `npm run dev`
> backend@1.0.0 dev
> ts-node src/index.ts
Running the app on port: 3000
// <- ここでファイルを変更
[nodemon] restarting due to changes...
[nodemon] starting `npm run dev`
> backend@1.0.0 dev
> ts-node src/index.ts
Running the app on port: 3000
-
このあたりの仕組みは、今後本番リリース時の可用性なども考えたら、変えた方が良い。例えば esbuild や webpack などのバンドラーを利用して、より小さくできるようにしておいた方が良さそう。その方がホットリロードも確実なはず。 ↩︎