encore-database について
encore-databaseは、Encore.tsのSQLデータベースを使う開発者向けのスキルです。クエリ、マイグレーション、サービス間のデータベース共有、Drizzle ORMに加え、テンプレートクエリによる値のエスケープを扱います。
主な機能
- 01非同期イテレーションと配列クエリ
- 02単一行クエリと書き込み操作
- 03位置パラメーター付きの生SQL
- 04順次実行するSQLマイグレーションの案内
- 05Drizzle ORMの設定と利用
- 06パラメーター化テンプレートクエリ
活用シーン
- 01Encoreサービスでのデータ行の読み取りや更新
- 02順次実行するデータベースマイグレーションの設定
- 03複数サービス間でのデータベース共有
- 04Encore SQLDatabaseでのDrizzle ORM利用
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ encoredev/ skills --skill encore-databaseインストール後、Claude Code で/encore-databaseを入力して読み込まれたか確認します。
詳細
Encore データベース操作
手順
データベースのセットアップ
import { SQLDatabase } from "encore.dev/storage/sqldb";
const db = new SQLDatabase("mydb", {
migrations: "./migrations",
});
クエリメソッド
Encore はいくつかのクエリメソッドを提供しています:
query - 複数行
複数行を返す非同期イテレーターを返します:
interface User {
id: string;
email: string;
name: string;
}
const rows = await db.query<User>`
SELECT id, email, name FROM users WHERE active = true
`;
const users: User[] = [];
for await (const row of rows) {
users.push(row);
}
queryAll - すべての行を配列として取得
すべての行を配列として返します(query の便利なラッパー):
const users = await db.queryAll<User>`
SELECT id, email, name FROM users WHERE active = true
`;
// users is User[]
queryRow - 単一行
1 行または null を返します:
const user = await db.queryRow<User>`
SELECT id, email, name FROM users WHERE id = ${userId}
`;
if (!user) {
throw APIError.notFound("user not found");
}
exec - 戻り値なし
INSERT、UPDATE、DELETE 操作に使用します:
await db.exec`
INSERT INTO users (id, email, name)
VALUES (${id}, ${email}, ${name})
`;
await db.exec`
UPDATE users SET name = ${newName} WHERE id = ${id}
`;
await db.exec`
DELETE FROM users WHERE id = ${id}
`;
生クエリメソッド
テンプレートリテラルの代わりに、位置パラメーター($1、$2 など)を使った生の SQL 文字列を使用します:
// Raw query returning multiple rows
const rows = await db.rawQuery<User>("SELECT * FROM users WHERE active = $1", true);
// Raw query returning single row
const user = await db.rawQueryRow<User>("SELECT * FROM users WHERE id = $1", userId);
// Raw query returning all rows as array
const users = await db.rawQueryAll<User>("SELECT * FROM users WHERE role = $1", "admin");
// Raw exec for INSERT/UPDATE/DELETE
await db.rawExec("INSERT INTO users (id, email) VALUES ($1, $2)", id, email);
サービス間でのデータベース共有
SQLDatabase.named() を使って、別のサービスが所有するデータベースを参照します:
import { SQLDatabase } from "encore.dev/storage/sqldb";
// In the service that owns the database
const db = new SQLDatabase("shared-db", {
migrations: "./migrations",
});
// In another service that needs access
const sharedDb = SQLDatabase.named("shared-db");
// Now you can query the shared database
const user = await sharedDb.queryRow<User>`SELECT * FROM users WHERE id = ${id}`;
マイグレーション
ファイル構造
service/
└── migrations/
├── 001_create_users.up.sql
├── 002_add_posts.up.sql
└── 003_add_indexes.up.sql
命名規則
- 数字で始める(001、002 など)
- その後にアンダースコアと説明を続ける
.up.sqlで終わる- 数字は連番でなければならない
マイグレーションの例
-- migrations/001_create_users.up.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users(email);
Drizzle ORM の統合
セットアップ
// db.ts
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { drizzle } from "drizzle-orm/node-postgres";
const db = new SQLDatabase("mydb", {
migrations: {
path: "migrations",
source: "drizzle",
},
});
export const orm = drizzle(db.connectionString);
スキーマ
// schema.ts
import * as p from "drizzle-orm/pg-core";
export const users = p.pgTable("users", {
id: p.uuid().primaryKey().defaultRandom(),
email: p.text().unique().notNull(),
name: p.text().notNull(),
createdAt: p.timestamp().defaultNow(),
});
Drizzle 設定
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "migrations",
schema: "schema.ts",
dialect: "postgresql",
});
マイグレーションを生成します:drizzle-kit generate
Drizzle の使用
import { orm } from "./db";
import { users } from "./schema";
import { eq } from "drizzle-orm";
// Select
const allUsers = await orm.select().from(users);
const user = await orm.select().from(users).where(eq(users.id, id));
// Insert
await orm.insert(users).values({ email, name });
// Update
await orm.update(users).set({ name }).where(eq(users.id, id));
// Delete
await orm.delete(users).where(eq(users.id, id));
SQL インジェクション対策
Encore のテンプレートリテラルは値を自動的にエスケープします:
// SAFE - values are parameterized
const email = "[email protected]";
await db.queryRow`SELECT * FROM users WHERE email = ${email}`;
// WRONG - SQL injection risk
await db.queryRow(`SELECT * FROM users WHERE email = '${email}'`);
ガイドライン
- クエリには常にテンプレートリテラルを使用する(自動エスケープ)
- ジェネリクスで型を指定する:
query<User>、queryRow<User> - マイグレーションは起動時に自動的に適用される
- 結果が 0 件または 1 件と予想される場合は
queryRowを使用する - 複数行の場合は非同期イテレーションとともに
queryを使用する - データベース名は小文字で説明的にする
- 通常、各サービスは独自のデータベースを持つ
コメント
Markdown 対応