Drizzle Kitでデータベースマイグレーションを行う
Drizzle Kit
Drizzle KitはDrizzleKitは、DrizzleORM向けのCLIマイグレーションツールである。
具体的には以下のようにできる。
- Typescriptのスキーマファイルをもとにマイグレーションファイルの作成。
- DBにマイグレーションファイルの適用。
- 既存のDBからTypescriptのスキーマファイルとマイグレーションファイルの作成。
今回はDrizzle Kitを使ってマイグレーションファイルを作成およびテーブルの作成を行う。
ディレクトリ構成
- docker-compose.yml・Dockerfile:DBの設定を行う。
- .env:DBの接続先を管理する。
- drizzle.config.ts:DBの接続先を設定を行う。
- schema.ts:スキーマの設定を行う。
├── drizzle
│ └── drizzle.config.ts
├── postgres
│ └── Dockerfile
├── src
│ └── infrastructure
│ └── config
│ └── schema.ts
├── .env
├── docker-compose.yml
└── package.json
※ schema.tsはGitで管理したい+Drizzle ORMで使用することがあるのでsrc配下に配置している。
※ drizzle.config.tsはGitで管理したくないのでdrizzle配下に配置している。
DBを用意する(docker-compose.yml・Dockerfile)
DBはDockerで用意する。
version: '3.9'
services:
postgres:
container_name: postgres
build:
context: ./postgres
dockerfile: Dockerfile
environment:
POSTGRES_USER: 'admin'
POSTGRES_PASSWORD: 'password'
POSTGRES_DB: 'db'
POSTGRES_INITDB_ARGS: '--encoding=UTF8 --locale=C'
PGDATA: /var/lib/postgresql/data/pgdata
TZ: 'UTC'
ports:
- "5432:5432"
volumes:
- ./db/data:/var/lib/postgresql/data
- ./db/initdb.d:/docker-entrypoint-initdb.d
yamllint:
container_name: aggregate_yamllint
build:
context: ./yamllint
dockerfile: Dockerfile
volumes:
- .:/fastify
entrypoint: /usr/bin/yamllint
command: --help
FROM postgres:14
EXPOSE 5432
以下のコマンドでDBを起動する。
docker-compose up postgres
接続先の情報を.envファイルに追加しておく。
DATABASE_URL=postgresql://admin:password@localhost:5432/db?schema=public
DBの接続先を設定する(drizzle.config.ts)
drizzle-kitとdotenvをインストールする。
npm install -D drizzle-kit
npm install dotenv --save
Configuring Drizzle kitを参考にdrizzle.config.tsの設定を行う。
import type { Config } from "drizzle-kit";
import * as dotenv from "dotenv";
dotenv.config();
export default {
schema: "./src/infrastructure/config/schema.ts",
out: "./drizzle",
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL || "",
}
} satisfies Config;
- schema:スキーマファイルの配置場所を設定する。
- out:Drizzle Kitが作成したSQLの出力先を設定する。
- driver:ドライバー('pg' | 'mysql2' | 'better-sqlite' | 'libsql' | 'turso' | 'd1')
- dbCredentials:DBの接続先を設定する。dotenvを使用して.envファイルから読み込む。
スキーマファイルを設定する(schema.ts)
schema.tsを設定するにあたって、Drizzle ORMをインストールする。
npm i drizzle-orm
SQL schema declarationを参考に以下のようなテーブルを作成するようにschema.tsを作成する。
- userテーブル:姓と名を持つ。
- contactテーブル:電話番号とメールアドレスを持つ。
user テーブル
カラム名 | データ型 | 制約 |
---|---|---|
id | SERIAL | PRIMARY KEY |
created_at | TIMESTAMP | NOT NULL DEFAULT NOW() |
updated_at | TIMESTAMP | NOT NULL DEFAULT NOW() |
deleted_at | TIMESTAMP | |
first_name | VARCHAR(256) | NOT NULL |
last_name | VARCHAR(256) | NOT NULL |
contact テーブル
カラム名 | データ型 | 制約 |
---|---|---|
id | SERIAL | PRIMARY KEY |
created_at | TIMESTAMP | NOT NULL DEFAULT NOW() |
updated_at | TIMESTAMP | NOT NULL DEFAULT NOW() |
deleted_at | TIMESTAMP | |
phone_number | VARCHAR(20) | NOT NULL |
VARCHAR(256) | NOT NULL | |
user_id | INTEGER | REFERENCES user(id) |
import { integer, pgTable, serial, timestamp, varchar } from 'drizzle-orm/pg-core';
const id = {
id: serial('id').primaryKey(),
};
const timestamps = {
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
deletedAt: timestamp('deleted_at'),
};
const schemaBase = {
...id,
...timestamps,
};
export const user = pgTable('user', {
...schemaBase,
firstName: varchar('first_name', { length: 256 }).notNull(),
lastName: varchar('last_name', { length: 256 }).notNull(),
});
export const contact = pgTable('contact', {
...schemaBase,
phoneNumber: varchar('phone_number', { length: 20 }).notNull(),
email: varchar('email', { length: 256 }).notNull(),
userId: integer('user_id').references(() => user.id),
});
コマンドを設定する
List of commandsを参考に実行コマンドを設定していく。
Drizzle Kitには以下のコマンドが用意されている。
- generate:schema.tsからマイグレーションファイルを作成する。
- push:マイグレーションファイルをDBに適用する。
- introspect:DBからマイグレーションファイルとschema.tsを作成する。
必要な設定はdrizzle.config.tsに設定済みなので、オプションで指定して実行する。
"scripts": {
"drizzle:generate": "drizzle-kit generate:pg --config=./drizzle/drizzle.config.ts",
"drizzle:push": "drizzle-kit push:pg --config=./drizzle/drizzle.config.ts",
"drizzle:introspect": "drizzle-kit introspect:pg --config=./drizzle/drizzle.config.ts",
}
schema.tsからマイグレーションファイルを作成する(generate)
generateコマンドを実行する。
npm run drizzle:generate
> fastify@1.0.0 drizzle:generate
> drizzle-kit generate:pg --config=./drizzle/drizzle.config.ts
drizzle-kit: v0.20.7
drizzle-orm: v0.29.1
Reading config file 'D:\program\fastify\drizzle\drizzle.config.ts'
2 tables
contact 7 columns 0 indexes 1 fks
user 6 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle\0000_superb_magneto.sql 🚀
drizzle配下にマイグレーションファイルが作成されている。
└── drizzle
├── meta
│ ├── _journal.json
│ └── 0000_snapshot.json
├── 0000_superb_magneto.sql
└── drizzle.config.ts
作成されたマイグレーションファイルは以下のようになっている。
CREATE TABLE IF NOT EXISTS "contact" (
"id" serial PRIMARY KEY NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"phone_number" varchar(20) NOT NULL,
"email" varchar(256) NOT NULL,
"user_id" integer
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "user" (
"id" serial PRIMARY KEY NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"first_name" varchar(256) NOT NULL,
"last_name" varchar(256) NOT NULL
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "contact" ADD CONSTRAINT "contact_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
マイグレーションファイルを適用する(push)
pushコマンドを実行する。
npm run drizzle:push
> fastify@1.0.0 drizzle:push
> drizzle-kit push:pg --config=./drizzle/drizzle.config.ts
drizzle-kit: v0.20.7
drizzle-orm: v0.29.1
Custom config path was provided, using './drizzle/drizzle.config.ts'
Reading config file 'D:\program\fastify\drizzle\drizzle.config.ts'
[✓] Changes applied
postgreSQLにログインしてテーブルが作成されているかを確認する。
db=# \d user
Table "public.user"
Column | Type | Collation | Nullable | Default
------------+-----------------------------+-----------+----------+----------------------------------
id | integer | | not null | nextval('user_id_seq'::regclass)
created_at | timestamp without time zone | | not null | now()
updated_at | timestamp without time zone | | not null | now()
deleted_at | timestamp without time zone | | |
first_name | character varying(256) | | not null |
last_name | character varying(256) | | not null |
Indexes:
"user_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "contact" CONSTRAINT "contact_user_id_user_id_fk" FOREIGN KEY (user_id) REFERENCES "user"(id)
db=# \d contact
Table "public.contact"
Column | Type | Collation | Nullable | Default
--------------+-----------------------------+-----------+----------+-------------------------------------
id | integer | | not null | nextval('contact_id_seq'::regclass)
created_at | timestamp without time zone | | not null | now()
updated_at | timestamp without time zone | | not null | now()
deleted_at | timestamp without time zone | | |
phone_number | character varying(20) | | not null |
email | character varying(256) | | not null |
user_id | integer | | |
Indexes:
"contact_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"contact_user_id_user_id_fk" FOREIGN KEY (user_id) REFERENCES "user"(id)
なお、npm run drizzle:generate
を再実行すると、以下のようになり重複してSQLが実行されることはない。
> fastify@1.0.0 drizzle:push
> drizzle-kit push:pg --config=./drizzle/drizzle.config.ts
drizzle-kit: v0.20.7
drizzle-orm: v0.29.1
Custom config path was provided, using './drizzle/drizzle.config.ts'
Reading config file 'D:\program\fastify\drizzle\drizzle.config.ts'
[i] No changes detected
schema.tsを更新してみる
contactテーブルのemailからNOT NULL制約を削除してみる。
export const contact = pgTable('contact', {
...schemaBase,
phoneNumber: varchar('phone_number', { length: 20 }).notNull(),
email: varchar('email', { length: 256 }),
userId: integer('user_id').references(() => user.id),
});
再度generateコマンドを実行する。
npm run drizzle:generate
> fastify@1.0.0 drizzle:generate
> drizzle-kit generate:pg --config=./drizzle/drizzle.config.ts
drizzle-kit: v0.20.7
drizzle-orm: v0.29.1
Reading config file 'D:\program\fastify\drizzle\drizzle.config.ts'
2 tables
contact 7 columns 0 indexes 1 fks
user 6 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle\0001_productive_big_bertha.sql 🚀
ALTER TABLE "contact" ALTER COLUMN "email" DROP NOT NULL;
再度pushコマンドを実行する。
npm run drizzle:push
> fastify@1.0.0 drizzle:push
> drizzle-kit push:pg --config=./drizzle/drizzle.config.ts
drizzle-kit: v0.20.7
drizzle-orm: v0.29.1
Custom config path was provided, using './drizzle/drizzle.config.ts'
Reading config file 'D:\program\fastify\drizzle\drizzle.config.ts'
[✓] Changes applied
再度postgreSQLにログインして制約が削除されているかを確認する。
db=# \d contact
Table "public.contact"
Column | Type | Collation | Nullable | Default
--------------+-----------------------------+-----------+----------+-------------------------------------
id | integer | | not null | nextval('contact_id_seq'::regclass)
created_at | timestamp without time zone | | not null | now()
updated_at | timestamp without time zone | | not null | now()
deleted_at | timestamp without time zone | | |
phone_number | character varying(20) | | not null |
email | character varying(256) | | |
user_id | integer | | |
Indexes:
"contact_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"contact_user_id_user_id_fk" FOREIGN KEY (user_id) REFERENCES "user"(id)
DBからマイグレーションファイルとschema.tsを作成してみる(introspect)
introspectコマンドは既にDBにテーブルが作成されていて、マイグレーションファイルが存在しない場合に使用する。
drizzle.config.tsだけを残してファイルを一旦削除して以下のコマンドを実行する。
npm run drizzle:introspect
> fastify@1.0.0 drizzle:introspect
> drizzle-kit introspect:pg --config=./drizzle/drizzle.config.ts
drizzle-kit: v0.20.7
drizzle-orm: v0.29.1
Custom config path was provided, using './drizzle/drizzle.config.ts'
Reading config file 'D:\program\fastify\drizzle\drizzle.config.ts'
[✓] 2 tables fetched
[✓] 13 columns fetched
[✓] 0 enums fetched
[✓] 0 indexes fetched
[✓] 1 foreign keys fetched
[✓] Your SQL migration file ➜ drizzle\0000_smiling_skrulls.sql 🚀
[✓] You schema file is ready ➜ drizzle\schema.ts 🚀
schema.tsはdrizzle.config.tsのoutで設定した場所に一緒に出力される。
└── drizzle
├── meta
│ ├── _journal.json
│ └── 0000_snapshot.json
├── 0000_smiling_skrulls.sql
├── drizzle.config.ts
└── schema.ts
マイグレーションファイルはコメントアウトされた形で出力される。
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
CREATE TABLE IF NOT EXISTS "user" (
"id" serial PRIMARY KEY NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"first_name" varchar(256) NOT NULL,
"last_name" varchar(256) NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "contact" (
"id" serial PRIMARY KEY NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"phone_number" varchar(20) NOT NULL,
"email" varchar(256),
"user_id" integer
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "contact" ADD CONSTRAINT "contact_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
*/
Discussion