Drizzle ORMの使い方を理解するためのチュートリアル
本文書はDrizzle ORMに興味があるのでどのような機能を持っているのか動作確認してみたいという人を対象にDrizzle ORMを利用してデータベースにデータを登録する方法など基本的な機能について動作確認を行っています。本文書を一通り読み進めることでDrizzle ORMがどのようなものか基本的なことは理解できるはずです。最後にWEBフレームワークのHonoでDrizzleを利用して連携を確認しています。
Drizzle ORMはデータベースにMySQL, Postgre, SQLiteなど幅広いデータベースをサポートしていますが本文書ではSQLiteを利用しています。
目次
Drizzle ORMとは
Drizzle ORMはSQL-likeなコードでデータベースを管理/操作することができるTypeScript ORMです。TypeScript ORMといえばPrismaを最初に思い浮かべる人も多いかと思います。DrizzleはPrismaと同様に定義したスキーマから型を生成することができ、型安全にアプリケーションの開発を行うことができます。
最近ではHeadless CMSの一つであるPayload CMS(https://payloadcms.com/)でもDrizzle ORMを利用することでデータベースにPostgresDBを利用できるようになっています。
ORMはObject Relational Mappingの略で、MySQLやPostgreSQL, SQLiteのようなリレーショナルベースに対してSQLではなくオブジェクトのメソッドを利用して操作を行うことができます。オブジェクトメソッドがどのようなものかは本書を読み進めるうちに理解することができます。
環境の構築
TypeScript環境の設定
TypeScriptを利用できる環境を構築するため動作確認用のディレクトリを作成します。ここではfirst-drizzleという名前にしていますが任意の名前をつけてください。
% mkdir first-drizzle
作成したfirst-drizzle ディレクトリに移動して”npm init -y”コマンドを実行します。”npm init -y”コマンドを実行するとpackage.jsonファイルが作成されます。
% cd first-drizzle
% npm init -y
TypeScriptを利用するために必要となるパッケージのインストールを行います。
% npm install typescript ts-node @types/node --save-dev
“npx tsc –init”コマンドを実行してTypeScriptの設定ファイルtsconfig.tsファイルを作成します。
% npx tsc --init
Created a new tsconfig.json with:
TS
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
You can learn more at https://aka.ms/tsconfig
TypeScriptを利用するための設定は完了です。
Drizzleのインストール
Drizzleを利用するために必要となるパッケージのインストールを行います。better-sqlite3はSQLiteデータベースを利用するために必要となるパッケージです。接続するデータベースによってインストールするパッケージは異なります。
% npm install drizzle-orm better-sqlite3
better-sqlite3のTypeのインストールも行います。
% npm i --save-dev @types/better-sqlite3
drizzle-kitは定義したスキーマファイルを利用してテーブルを作成/更新するために必要なマイグレーションファイルを作成するためのツールです。
% npm install -D drizzle-kit
ここまでにインストールしたパッケージの確認を行うためpackage.jsonファイルの中身を確認します。
{
"name": "first-drizzle",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/better-sqlite3": "^7.6.11",
"@types/node": "^20.14.12",
"drizzle-kit": "^0.23.0",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
},
"dependencies": {
"better-sqlite3": "^11.1.2",
"drizzle-orm": "^0.32.1"
}
}
Drizzleの設定
スキーマファイルやデータベースの接続用のコードを保存するためのプロジェクトディレクトリ直下にdbディレクトリの作成を行います。
% mkdir db
スキーマファイルの作成
スキーマはデータベースの構造を定義するための情報です。簡単にいうとテーブルがどのような列名で構成され、それらの列にどのようなデータを保存するかを定義することです。
スキーマファイルは1つのファイルにまとめてスキーマを記述する方法とスキーマ毎にファイルを分ける方法がありますが本文書では1つのファイルを利用してスキーマを定義します。dbディレクトリにschema.tsファイルを作成し、todosテーブルをSQLiteデータベースに作成するために以下のスキーマを設定します。todosテーブルはid, name, isCompletedの列で構成され、idはオートインクレメントを設定した列を追加する度に自動で数値が採番されます。nameは文字列が格納されisCompletedには数値が格納されます。
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';
export const todos = sqliteTable('todos', {
id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }),
name: text('name'),
isCompleted: integer('isCompleted', { mode: 'boolean' }).notNull().default(false),
});
Migrationファイルの作成
スキーマファイルに記述した内容を元にDrizzle Kitを利用してマイグレーションファイルを作成します。実行する際には–dialectオプションにsqlite, —schema オプション schema.ts ファイルを指定します。実行すると drizzle ディレクトリが作成され、mata ディレクトリと sql ファイルが作成されます。ファイル名は自動で命名されます。metaディレクトリの中にはマイグレーションを管理する情報が保存されます。
% npx drizzle-kit generate --dialect='sqlite' --schema=./db/schema.ts
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
1 tables
todos 3 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle\0000_wandering_blue_blade.sql
ファイルの拡張子がsqlという名前がつけている通り0000_amazing_firestar.sqlの中にはDDL(Data Definition Language)が記述されておりテーブル作成に利用することができるSQLのcreate文が記述されています。create文なのでそのままSQLiteに接続してテーブルを作成することができます。
CREATE TABLE `todos` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text,
`isCompleted` integer DEFAULT false NOT NULL
);
DBへの接続
データベースへの接続に利用するためのコードを記述するためdb.tsファイルをdbディレクトリの下に作成します。
import { drizzle, BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
import Database from 'better-sqlite3';
const sqlite = new Database('./db/sqlite.db');
export const db: BetterSQLite3Database = drizzle(sqlite);
migrate(db, { migrationsFolder: './drizzle' });
最後の行にmigrate関数が記述していますがこの1行がマイグレーションファイルを元にデータベースの作成やテーブルの作成/更新などを行います。テーブルの構成を変更した場合など必要な時にのみ実行されます。migrationFolderプロパティにマイグレーションファイルが保存されているdrizzleディレクトリを指定します。
SQLiteは先ほど説明した通りファイルベースなのでsqlite.dbという名前でdbディレクトリに保存されるように設定しています。
Drizzleの動作確認
これまでに作成した情報を利用してデータベースのテーブルを操作するコードを記述するためプロジェクトディレクトリ直下にindex.tsファイルを作成します。
index.tsファイルではSQLiteデータベースにアクセスを行うtodosテーブルからデータを取得します。db.select().from(todos)のfromメソッドにはデータを取得していテーブルの情報を指定しています。todosはschema.tsファイルからimportしています。すべてのデータを取得するためallメソッドを設定しています。
import { db } from './db/db';
import { todos } from './db/schema';
function main() {
const allTodo = db.select().from(todos).all();
console.log(allTodo);
}
main();
作成したindex.tsファイルを実行します。実行してもtodosテーブルには何もデータが入っていないため空の配列が表示されます。
% npx ts-node index.ts
[]
実行後dbディレクトリを確認するとsqlite.dbファイルが作成されていることがわかります。
データの登録
insertメソッドを利用してテーブルへデータの登録を行うためにindex.tsファイルの更新を行います。実行するためにはrunメソッドも必要です。
import { db } from './db/db';
import { todos } from './db/schema';
async function main() {
const result = db
.insert(todos)
.values({ name: 'Learn Drizzle', isCompleted: false })
.run();
console.log('result', result);
const allTodo = db.select().from(todos).all();
console.log('allTodo', allTodo);
}
main();
index.tsファイルを実行すると先ほどとは異なり、selectメソッドを利用して登録したデータが取得できていることが確認できます。
% npx ts-node index.ts
result { changes: 1, lastInsertRowid: 1 }
allTodo [ { id: 1, name: 'Learn Drizzle', isCompleted: false } ]
Drizzle ORMを利用してSQLiteデータベースへデータが登録できるようになりました。
allでははく10件文のデータを取得したい場合にはlimitメソッドを利用することができます。
const allTodo = db.select().from(todos).liimit().all();
allメソッドで取得したデータ件数はlengthをつけて確認することができます。
const allTodo = db.select().from(todos).all().length();
getメソッドを利用すると1件のデータを取得することができます。
const singleTodo = db.select().from(todos).get();
//
const singleTodo = db.select().from(todos).limit().get();
マイグレーションの動作確認
スキーマファイルの変更を行った場合のデータベースのテーブルに反映させるための方法を確認してみます。
列の追加
schema.tsファイルの実行済みのスキーマにuserId列を追加します。
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';
export const todos = sqliteTable('todos', {
id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }),
name: text('name'),
userId: text('useId'),
isCompleted: integer('isCompleted', { mode: 'boolean' })
.notNull()
.default(false),
});
schema.tsファイルを更新後、drizzle-kitを利用してマイグレーションファイルを作成を行います。
% npx drizzle-kit generate --dialect='sqlite' --schema=./db/schema.ts
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
1 tables
todos 4 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle/0001_busy_psynapse.sql 🚀
新たにdrizzleディレクトリに0001_flippant_sway.sqlファイルが作成されます。中身を確認すると追加したdate列に関するalter table文が記述されています。
ALTER TABLE todos ADD `useId` text;
マイグレーションがテーブルに反映されるのか確認するためにindex.tsを更新してselect文を実行します。
import { db } from './db/db';
import { todos } from './db/schema';
async function main() {
const allTodo = db.select().from(todos).all();
console.log('allTodo', allTodo);
}
main();
取得したデータにuserId列が追加されていることができます。作成されたマイグレーションファイルを元にテーブルが更新されています。値にはnullが設定されています。
% npx ts-node index.ts
allTodo [ { id: 1, name: 'Learn Drizzle', userId: null, isCompleted: false } ]
列の削除
列の追加を行うことができたので次は追加した列を削除したい場合の動作確認を行います。追加したuserId列を削除するためにスキーマファイルを更新します。
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';
export const todos = sqliteTable('todos', {
id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }),
name: text('name'),
isCompleted: integer('isCompleted', { mode: 'boolean' })
.notNull()
.default(false),
});
schema.tsファイルを更新後、マイグレーションファイルを作成を行います。
% npx drizzle-kit generate --dialect='sqlite' --schema=./db/schema.ts
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
1 tables
todos 3 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle/0002_volatile_cannonball.sql 🚀
0002_volatile_cannonball.sqlファイルには列の削除を行うAlter TABLE分が記述されています。
ALTER TABLE `todos` DROP COLUMN `useId`;
マイグレーションの内容を反映させるためにindex.tsファイルを実行します。実行すると以下のuserIdが削除されていることが確認できます。
% npx ts-node index.ts
allTodo [ { id: 1, name: 'Learn Drizzle', isCompleted: false } ]
設定ファイル
Drizzle Kitでは設定ファイルを利用することができます。drizzle.config.tsファイルをプロジェクトフォルダ直下に作成してスキーマフォルダやマイグレーションフォルダを指定することができます。
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./db/schema.ts",
out: "./drizzle",
});
drizzle.config.tsファイルを作成後はnpx drizzle-kit generateを実行する際にオプションに–dialect, –schemaを設定していましたがdrizzle.config.tsファイルに記述されているため省略することができます。
Drop Migration
Drizzle KitにはDrop Migrationを行うコマンドがあります。drizzle.config.tsにdialect, schema, outが設定されているのでそれらの情報を利用して処理が行われます。実行するとこれまで作成したマイグレーションの名前が表示されます。選択することでマイグレーションファイルを削除することはできますが削除してもデータベースのテーブルに反映されるわけではありません。
% npx drizzle-kit drop
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
No config path provided, using default 'drizzle.config.ts'
Please select migration to drop:
0000_amazing_firestar
0001_busy_psynapse
❯ 0002_volatile_cannonball
Drizzle Studio
Drizzle Studioを利用することでブラウザ経由でデータベースのテーブルの中身を確認することができます。
Drizzle Studioを利用する前にdrizzle.config.tsファイルでdbCredentialsの設定を行う必要があります。dbCredentialsにSQLiteデータベースのファイルのパスを指定します。dbCredentialsの設定がない場合には”Invalid input Please specify a ‘dbCredentials’ param in config.”のエラーメッセージが表示されます。
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./db/schema.ts",
out: "./drizzle",
dbCredentials: {
url: "./db/sqlite.db",
},
});
設定が完了したら”npx drizzle-kit studio”コマンドを実行します。
npx drizzle-kit studio
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
No config path provided, using default path
Reading config file 'C:\Users\Kazu\first-drizzle\drizzle.config.ts'
Warning Drizzle Studio is currently in Beta. If you find anything that is not working as expected or should be improved, feel free to create an issue on GitHub: https://github.com/drizzle-team/drizzle-kit-mirror/issues/new or write to us on Discord: https://discord.gg/WcRKz2FFxN
Drizzle Studio is up and running on https://local.drizzle.studio
ブラウザからhttps://local.drizzle.studioにアクセスを行います。下記のようにブラウザ上でテーブルを確認するだけではなくデータの更新や追加も行うことができます。
その他
Typeの設定(InferModel)
index.tsファイルの中でinsertの処理を別の関数insertTodoに分けた場合に引数に型をしない場合のコードを記述します。
import { db } from './db/db';
import { todos } from './db/schema';
const InsertTodo = (todo) => {
return db.insert(todos).values(todo).run();
};
async function main() {
const result = insertTodo({ name: 'Learn TypeScript', isCompleted: false });
console.log('result', result);
const allTodo = db.select().from(todos).all();
console.log(allTodo);
}
には以下のようにメッセージが表示されます。
型を設定するためにInferInsertModelを利用することができます。InferInsertModelを利用して作成した型InsertTodoをinsertTodoの引数のtodoの型として利用することができます。
type insertTodo = InferInsertModel<typeof todos>;
import { db } from './db/db';
import { todos } from './db/schema';
import { InferInsertModel } from 'drizzle-orm';
type InsertTodo = InferInsertModel<typeof todos>;
const insertTodo = (todo: InsertTodo) => {
return db.insert(todos).values(todo).run();
};
async function main() {
const result = insertTodo({ name: 'Learn TypeScript', isCompleted: 0 });
console.log('result', result);
const allTodo = db.select().from(todos).all();
console.log(allTodo);
}
selectから取得する値に対する型が必要な場合にもInforModeを利用することができます。
import { db } from './db/db';
import { todos } from './db/schema';
import { InferInsertModel, InferSelectModel } from 'drizzle-orm';
type insertTodo = InferInsertModel<typeof todos>;
type Todo = InferSelectModel<typeof todos>;
const insertTodo = (todo: InsertTodo) => {
return db.insert(todos).values(todo).run();
};
async function main() {
const result = insertTodo({ name: 'Learn TypeScript', isCompleted: 0 });
console.log('result', result);
const allTodo: Todo[] = db.select().from(todos).all();
console.log(allTodo);
}
main();
それぞれの型情報も確認しておきます。
実行したSQLの中身
実行したSQLの中身を確認したい場合にはtoSQLメソッドを利用することができます。
import { db } from './db/db';
import { todos } from './db/schema';
async function main() {
const query = db.select().from(todos).toSQL();
console.log(query);
}
main();
index.tsを実行すると実行したSQLが’select “id”, “name”, “isCompleted” from “todos”‘であることがわかります。
% npx ts-node index.ts
{ sql: 'select "id", "name", "isCompleted" from "todos"', params: [] }
Loggingの設定
実行したSQLの中身を確認するためにloggingの設定を行うことができます。設定はdb/db.tsファイルで行います。
export const db: BetterSQLite3Database = drizzle(sqlite, { logger: true });
下記のindex.tsファイルを実行します。
import { db } from './db/db';
import { todos } from './db/schema';
async function main() {
const query = db.select().from(todos).toSQL();
console.log(query);
}
main();
todosテーブルへのSQL以外にもQueryを確認することができます。migrationに関連するテーブルへのアクセスが行われています。
% npx ts-node index.ts
Query:
CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
id SERIAL PRIMARY KEY,
hash text NOT NULL,
created_at numeric
)
Query: SELECT id, hash, created_at FROM "__drizzle_migrations" ORDER BY created_at DESC LIMIT 1
Query: BEGIN
Query: COMMIT
{ sql: 'select "id", "name", "isCompleted" from "todos"', params: [] }
SQLiteデータベースにはtodosテーブルの他にマイグレーションを管理するための__drizzle_migrationsテーブルが作成されます。db.tsファイルからmigrate関数の行を削除するとtodosへのSQL分のみとなります。
【付録】Honoと一緒に使ってみた
ここまでの動作確認ではDrizzle単独で利用してきたので最近耳にする機会の増えたWEBフレームワークHonoでDrizzleを使ってみましょう。他のフレームワークを利用しても設定方法などはすべて同じです。Honoだからといって特別に行うことはありません。データベースにはこれまでと同様にSQLiteデータベースを利用します。
Honoプロジェクトの作成
Honoのインストールは”npm create hono@latest”コマンドで行います。aws-lambdaからnextjs, cloudflare-pagesなどさまざまなテンプレートが用意されていますがここではnodejsを選択してインストールしています。
% npm create hono@latest hono_drizzle
create-hono version 0.10.1
✔ Using target directory … hono_drizzle
? Which template do you want to use? nodejs
√ Cloning the template
? Do you want to install project dependencies? yes
? Which package manager do you want to use? npm
√ Installing project dependencies
🎉 Copied project files
Get started with: cd hono_drizzle
プロジェクトの作成が完了するとプロジェクトディレクトリが作成されるので移動をして”npm run dev”コマンドを実行します。
% npm run dev
> dev
> tsx watch src/index.ts
Server is running on port 3000
どのようなコードが記述されているかsrcディレクトリのindex.tsファイルを確認します。
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
const port = 3000
console.log(`Server is running on port ${port}`)
serve({
fetch: app.fetch,
port
})
Honoを知らなくても”/”にアクセスすると”Hello Hono!”が戻されることはわかります。ポートは3000に設定されているのでブラウザからhttp://localhost:3000にアクセスしてみましょう。
Drizzleの設定
Drizzleで利用するライブラリは先ほどと同じです。
% npm install drizzle-orm better-sqlite3
better-sqlite3のTypeのインストールも行います。
% npm i --save-dev @types/better-sqlite3
drizzle-kitは定義したスキーマファイルを利用してテーブルを作成/更新するために必要なマイグレーションファイルを作成するためのツールです。
% npm install -D drizzle-kit
srcディレクトリにdbディレクトリを作成してschema.tsファイルを作成して以下のコードを記述します。
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';
export const todos = sqliteTable('todos', {
id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }),
name: text('name'),
isCompleted: integer('isCompleted', { mode: 'boolean' }).notNull().default(false),
});
srcディレクトリの下にdrizzle.config.tsファイルを作成します。
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./src/db/schema.ts",
out: "./src/drizzle",
dbCredentials: {
url: "./src/db/db.sqlite",
},
});
作成済みのschema.tsファイルを元にマイグレーションファイルを作成するため”npx drizzle-kit generate”コマンドを実行します。
% npx drizzle-kit generate
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
No config path provided, using default 'drizzle.config.ts'
Reading config file 'C:\Users\Reffct\Desktop\hono_drizzle\drizzle.config.ts'
1 tables
todos 4 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ src\drizzle\0000_third_lethal_legion.sql
コマンドを実行するとsrcディレクトリの下にdrizzleディレクトリが作成され、0000_third_lethal_legion.sqlが作成されます。
マイグレーションファイルを利用してデータベースファイルとテーブルを作成するために”npx drizzle-kit migrate”コマンドを実行します。
% npx drizzle-kit migrate
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
No config path provided, using default path
Reading config file 'C:\Users\Reffect\Desktop\hono_drizzle\drizzle.config.ts'
[✓] migrations applied successfully!
実行するとdbディレクトリの下にdb.sqliteファイルが作成されます。
テーブルが作成されているか確認するためにDrizzle Studioを起動します。
% npx drizzle-kit studio
drizzle-kit: v0.23.0
drizzle-orm: v0.32.1
No config path provided, using default path
Reading config file 'C:\Users\Reffect\Desktop\hono_drizzle\drizzle.config.ts'
Warning Drizzle Studio is currently in Beta. If you find anything that is not working as expected or should be improved, feel free to create an issue on GitHub: https://github.com/drizzle-team/drizzle-kit-mirror/issues/new or write to us on Discord: https://discord.gg/WcRKz2FFxN
Drizzle Studio is up and running on https://local.drizzle.studio
ブラウザからhttps://local.drizzle.studioにアクセスします。
HonoからDrizzle経由でSQLiteデータベースにアクセスできるようにdbディレクトリにdb.tsファイルを作成します。
import { drizzle, BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import Database from "better-sqlite3";
const sqlite = new Database("./src/db/db.sqlite");
export const db: BetterSQLite3Database = drizzle(sqlite);
Honoからのアクセス
HonoからDrizzleを経由してデータベースからデータを取得するためにsrc/index.tsファイルに新たにルーティングを追加します。
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { db } from './db/db'
import { todos } from './db/schema'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
app.get("/api/todos", (c) => {
const allTodo = db.select().from(todos).all();
return c.json({
allTodo,
});
});
const port = 3000
console.log(`Server is running on port ${port}`)
serve({
fetch: app.fetch,
port
})
VSCodeのExtensiontsのThunder Clientを利用して/api/todosに対してGETリクエストを送信します。
/api/todosからJSONでデータベースに挿入した2件のデータが戻されることが確認できます。
このように簡単にフレームワークと連携させてデータベースからDrizzle経由でデータを取得することができます。