| | | |

[Backend] : Express + Prisma + TypeScriptのセットアップ

Webアプリを作る勉強をする中で、フロントとデータベースを繋ぐ部分がイメージしづらかったりする。フロントはブラウザーで実際に見える画面であり、フロントで表示するためにユーザー情報などのデータを格納しているのがデータベース(以下DB)。DBにあるデータに何か条件を加えたりして表示をする。ここまではプログラミングの勉強を始めたばかりの人でもイメージしやすい部分だと思う。

ところがReact + TypeScriptでフロント側からアプリを作り始めて、さてそろそろデータベース側をと思うと、う〜ん…どうやるんだっけ?ってなるので復習しながらテンプレートを作っていく。(テンプレートだけ欲しい場合はこちら

Node.js

まず、今回取り上げるExpressもPrismaもNode.jsでバックエンドのアプリ構築に使用するものなのでNode.jsが何かを思い出しておく😅

Node.js

JavaScriptはもともとフロント側でのみ使われるプログラミング言語だった。それをサーバー側でも使えるようにしたのがNode.js。

https://blog.platypuscode.com/2023%e5%b9%b4%e6%8c%af%e3%82%8a%e8%bf%94%e3%82%8a/

思い出しついでに、最近ようやく理解したことがあるので書き留めておく。
Node.jsが必要と書いてあったので、Nodeがインストールされているか確認するためにpackage.jsonを見たがNodeが見当たらない。でもバージョン確認をするとv18.14.1 と返ってきた。これは??

node -v

# v18.14.1

このあたり、色々ごっちゃになってしまっていたが、Node.jsはシステム(MacのOSなど)にグローバルにインストールするもので、システム全体で使用するものだそう。そのためpackage.json内の依存関係としては記載されてない。とするとpackage.jsonは何なのか?package.jsonはこのアプリを動かすために必要なライブラリーなどのDependencies(依存関係)とそのバージョン、および”dev”や”build”などのスクリプトを保存しているファイルで、Node.jsの中にあるnpm(node package module)用の情報をまとめている。

このことを踏まえてこちらの公式サイトをあらためて見ると、確かにそう書いてあるわ!

ただ、私のOSにあるNode.jsのバージョンが古いようなので、アップデートしておこう。

アップデート方法も一応書いておく。

  1. nvm(Node Version Manager)のインストール
  2. シェルプロファイルの再読み込み
    nvmをインストールした後、シェルプロファイル(.zshrc、.bashrcなど)を再読み込みする
  3. nvmを使って最新のLTSバージョンのNode.jsをインストール
  4. インストールが完了したら、以下のコマンドで正しいバージョンがインストールされたことを確認
  5. npmのバージョンを確認
  6. デフォルトのNode.jsバージョンの設定

※Codeの行番号は上記リスト番号に対応。以下1行ずつ実行していく。

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.zshrc # または ~/.bashrc
nvm install 20
node -v  # `v20.15.0` と表示される
npm -v  # `10.7.0` と表示される
nvm alias default 20

公式の新規ダウンロード手順と違うのは2. シェルプロファイルの再読み込み。新しい設定や変更を現在のシェルセッションに即座に反映させるための手順。シェルプロファイルの再読み込みとは、シェル(ターミナル)で使用される設定ファイルを再度読み込んで、その内容を反映させることを指す。どちらを使っているのか調べようと思ったらターミナルの上の方にzshと普通に書いてあった。

  • ~/.zshrc:Zshシェルの設定ファイル
  • ~/.bashrc:Bashシェルの設定ファイル

npm

新規レポジトリーを作ってそれをテンプレートにしていこうと思うのでまずはpackage.jsonを作るためにnpm initをする。ここで-yをつけて実行するとpackage nameなどに関する質問に全てyesしたことになってささっと作れる。

npm init -y

さて、ようやく準備が整ったのでここからが今日の本題。

Express

こちらのサイトに細かく書いてある通りなのだが、今回はExpressをインストールする方法と何のためにExpressを使うかところを理解する。

まずインストール方法。
1つ目は普通にexpress自体のinstall。
2つ目はTypeScriptのinstall。
3つ目はExpressのTypeScript用型定義ファイルであり、開発時にコード補完や型チェックのために必要なので devDependencies に追加する。(これを忘れるとimportのところで赤波線エラーがでてnpm i –save-dev @types/express を実行してみるようにとメッセージが出る)(ちなみにnpm i @types/express –save-dev と後ろにつけても問題なし)
4つ目はTypeScriptファイルをスクリプトで直接実行できるようにts-nodeをdevDependenciesに追加する (installされていない場合には最後npm run start:backendでスクリプト実行するときにts-node: command not foundというエラーが表示される)
5つ目はnodeのTypeScript用型定義ファイル

npm install express
npm install typescript --save-dev
npm i --save-dev @types/express
npm i --save-dev ts-node
npm i --save-dev @types/node

2〜5を一気に実行することも可能。

npm install typescript @types/express ts-node @types/node --save-dev

次に以下を実行してtsconfig.jsonというファイルを作成する。

npx tsc --init

中身を置き換える。

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "moduleResolution": "node",
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

次に最低限のフォルダとファイルを作る。src、distというフォルダを作り、srcの中にindex.tsファイルを作成する。

/
|-- src/
|   |-- index.ts
|-- dist/
|-- node_modules/
|-- package-lock.json
|-- package.json
|-- tsconfig.json

src/index.tsファイルの中に以下のコードをコピペする。これがExpress serverになる。

import express from 'express';

const app = express();
const port = 3000;
//const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

package.jsonのscriptに、以下スクリプトを追加してTypeScriptで書かれたExpressサーバーを簡単に起動できるようにする。

"scripts": {
    "start": "ts-node src/index.ts", // 追加
    "test": "echo \"Error: no test specified\" && exit 1"
  },

このscriptはnpm startで実行できる(本当はnpm run startだが、runは省略できる)。また実行がうまくいった場合はServer running on port 3000とconsole.logの内容が表示される。Expressサーバーが起動した。

npm start

# Server running on port 3000

Expressは何のためのツールか?

Expressとは、Node.jsのHTTPモジュールを使って、より効率的にWebサーバーを構築できるようにするためのツールである。実はNode.jsにはHTTPモジュールが元から組み込まれているので、Expressを使わなくても同じ事が実現できるのだが、Expressを挟むと、コードがもっと書きやすくなるというわけ。

サーバー: HTTP(Hypertext Transfer Protocol)を使用して、クライアントのリクエストに応じてデータやサービスを提供する。具体的な応答を生成し、管理する役割を持つ。

gitignore

変更ファイル数がえげつない数で表示されていると思ったら.gitignoreファイルを作るのを忘れていた。

/
|-- src/
|   |-- index.ts
|-- dist/
|-- .gitignore
|-- node_modules/
|-- package-lock.json
|-- package.json
|-- tsconfig.json

.gitignoreファイルを作成し、以下貼り付ける

# Node.js
node_modules/

# TypeScript
dist/
*.tsbuildinfo

# Environment variables
.env

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Mac OS
.DS_Store

Prisma

気を取り直して次にこちらの公式ページを参考にしてPrismaをインストールする。先ほどすでにTypeScriptなどはインストール済みなので、Prismaの部分から実行する。

npm install prisma --save-dev
npx prisma init --datasource-provider sqlite

すると以下のようなメッセージが表示された。
prisma/schema.prismaというファイルが作成された。これは後で見ていこう。
そして.envファイルも作成されている。.envをgitignore fileに入れてねというwarningが出ているがこちらは対応済み。6行目から次のステップが書かれているが、よく分からないので調べる。

# ✔ 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. Run prisma db pull to turn your database schema into a Prisma schema.
# 3. 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

ただ、その前にPrismaとは何か、何のためにインストールしたのかを理解する。

ORM(Object-Relational Mapping)

Prismaについて調べるとORMツールですよと書いてあるが、ORMが何なのか分かっていないと先に進めない。ORMとは、Object-Relational Mappingの略で、日本語では「オブジェクト関係マッピング」と言うらしい。このツールを使うと、オブジェクトの形になっているプログラミングのコードとテーブルの形になっているデータベースの紐付けがされるので、データベースの行や列を、コード内で扱いやすくなるのだ。しかも、ORMツールの中でもPrismaはTypeScriptを使ってType safeなコードを書くことができるので、採用する。(ちなみに、ORMを使わなかった場合は、SQLを使ってデータベース操作を行うことになる)

.env

.gitignoreに.envがリストアップされていることを確認したので、.envファイルを開いた。すでにDATABASE_URL="file:./dev.db"と書かれている。今回はこのままで問題ない。”file:./dev.db”とするとPrismaはSQLiteを使用して、現在のディレクトリに dev.db ファイルを作成し、「現在のディレクトリにある dev.db ファイル」がDATABASE_URLとして実行される。(もし今後Postgresなどでデータベースを管理することになった場合は10行目の例のようにDATABASE_URLを指定しなおす。)

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="file:./dev.db"

# Example
# DATABASE_URL="postgresql://username:password@localhost:5432/mydatabase"

先ほどのNext stepsに話を戻すと、1は上記の対応(SQLiteを使用)でOK。しかしその後の(Step 2) prisma db pullは不要。prisma db pullは既存のデータベーススキーマをPrismaスキーマに変換するためのコマンドで、既存のデータベースがない場合には不要だった。また、この後説明するprisma migrate dev –name initを実行すれば、自動的にPrisma Clientも生成されるため、Step 3のprisma generateも別途実行する必要がない。

# 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. Run prisma db pull to turn your database schema into a Prisma schema.
# 3. Run prisma generate to generate the Prisma Client. You can then start querying your database.

というわけで、先に進もう。

Prismaスキーマ

prisma/schema.prismaファイルを開き、データモデルを定義する。試しにmodel Userを追加してみた。スキーマファイルは、Prisma ORMを使用してデータベースの構造を定義するためのファイルで、ここには生成するクライアントの設定、データベース接続の設定、データモデルの定義などが含まれる。

// 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 = "sqlite"
  url      = env("DATABASE_URL")
}

model User {
  id    Int    @id @default(autoincrement())
  name  String
  email String @unique
}

初期マイグレーション

次にマイグレーションを作成してデータベースの初期設定を行う。これをすると先ほど設定したスキーマを元に必要なテーブルやインデックスがデータベースに作成される。なお初期マイグレーションを作成すると、通常は自動的にPrisma Clientも生成される(以下28行目)。

npx prisma migrate dev --name init

# Environment variables loaded from .env
#Prisma schema loaded from prisma/schema.prisma
# Datasource "db": SQLite database "dev.db" at "file:./dev.db"

# SQLite database dev.db created at file:./dev.db

# Applying migration `20240703124541_init`

# The following migration(s) have been created and applied from new schema changes:

# migrations/
#   └─ 20240703124541_init/
#     └─ migration.sql

# Your database is now in sync with your schema.

# Running generate... (Use --skip-generate to skip the generators)

# added 1 package, and audited 102 packages in 3s

# 12 packages are looking for funding
#   run `npm fund` for details

# found 0 vulnerabilities

# ✔ Generated Prisma Client (v5.16.1) to ./node_modules/@prisma/client in 49ms

Seed.ts ファイル

seed.tsを作成する。何のためかというと、開発やテストする際に、ダミーデータを使いたいから。公式の解説はこちら

/
|-- src/
|   |-- index.ts
|-- dist/
|-- .gitignore
|-- node_modules/
|-- prisma/
|   |-- migrations/
|   |   |-- 20240703124541_init/
|   |   |-- migration_lock.toml
|   |-- dev.db
|   |-- dev.db-journal
|   |-- schema.prisma
|   |-- seed.ts
|-- package-lock.json
|-- package.json
|-- tsconfig.json

seed.tsにはこのようにダミーデータを作るためのfunctionを書く。

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function main() {
  // Example seed data
  const users = [
    {
      name: 'Alice',
      email: 'alice@example.com',
    },
    {
      name: 'Bob',
      email: 'bob@example.com',
    },
  ];

  for (const user of users) {
    await prisma.user.create({
      data: user,
    });
  }

  console.log('Seed data inserted successfully.');

  // Disconnect Prisma Client
  await prisma.$disconnect();
}

main()
  .catch((e) => {
    console.error('Error seeding data:', e);
    process.exit(1);
  })
  .finally(async () => {
    // Ensure Prisma Client is disconnected
    await prisma.$disconnect();
  });

これを実行するとデータベースにデータが作成されるので、そのためのコマンドをpackage.jsonに設定する。

{
  "name": "express-prisma-template",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "ts-node src/index.ts",
    "build": "tsc",
    "dev": "nodemon src/index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "prisma": {
    "seed": "ts-node prisma/seed.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@prisma/client": "^5.16.1",
    "express": "^4.19.2"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20.14.8",
    "prisma": "^5.16.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.5.2"
  }
}

以下のコマンドを実行すると、seedを使ってデータベースが作られる。

npx prisma db seed

# Environment variables loaded from .env
# Running seed command `ts-node prisma/seed.ts` ...
# Seed data inserted successfully.

# 🌱  The seed command has been executed.

成功したって書いてあるけど、何がどうなったのかよく分からないのでPrisma Studioを開いて確認する。

Prisma Studio

Prisma Studioを起動する。

npx prisma studio

このコマンドを実行すると、ブラウザが開き、Prisma Studioが表示される。

先ほどSeedで作ったダミーのUserテーブルが表示されている。Userを開くと以下のようにテーブルが表示されるので、Seedが成功したと確認できる。

この状態にいつでも戻ってこられるので、Userを追加、削除、編集する機能を開発中に、データベースをリセットする場合などに使用できる。

ここでは直接テーブルを書き換えてみる。Bobを削除して、Johnを追加してみた。

データベースを初期設定に戻したい場合は以下のコマンドを実行する。すると”? Are you sure you want to reset your database? All data will be lost. › (y/N)”という質問が表示されるので、yを入力。あとは自動で色々実行されて、再度Prisma Studioを起動し、テーブルの中身を確認すると、Seedの状態に戻っている。

npx prisma migrate reset

# Environment variables loaded from .env
# Prisma schema loaded from prisma/schema.prisma
# Datasource "db": SQLite database "dev.db" at "file:./dev.db"

# ? Are you sure you want to reset your database? All data will be lost. › (y/N)

# Applying migration `20240703124541_init`

# Database reset successful

# The following migration(s) have been applied:

# migrations/
#   └─ 20240703124541_init/
#     └─ migration.sql

# ✔ Generated Prisma Client (v5.16.1) to ./node_modules/@prisma/client in 48ms

# Running seed command `ts-node prisma/seed.ts` ...
# Seed data inserted successfully.

# 🌱  The seed command has been executed.

Template

ここまでをコミットして、GitHubでBanckend用のTemplateにしておいた。これで次からセットアップ済みのものを使えるようになる。テンプレートはこちら


今日は苦手なバックエンドの話だったので、
躓きまくり、脱線しまくりでした。
普段からこんな感じの勉強の仕方が多いのですが
ごちゃごちゃした私の頭の中の整理に
お付き合いいただいた皆様ありがとうございました。

類似投稿