encore-go-database について
EncoreのGo SQLデータベースAPIを使うためのリファレンススキルです。Go開発者向けに、クエリ、マイグレーション、トランザクション、パラメーター化クエリの扱いを説明します。
主な機能
- 01sqldbによるデータベース設定
- 02Query、QueryRow、Execの使い方
- 03マイグレーションの命名と構成
- 04トランザクションとScanの指針
- 05パラメーター化クエリとエラー処理
活用シーン
- 01Encore Goサービスのデータベースアクセス実装
- 02SQLマイグレーションの追加と整理
- 03クエリやトランザクションの書き方の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ encoredev/ skills --skill encore-go-databaseインストール後、Claude Code で/encore-go-databaseを入力して読み込まれたか確認します。
詳細
Encore Go データベース操作
手順
データベースのセットアップ
package user
import "encore.dev/storage/sqldb"
var db = sqldb.NewDatabase("userdb", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
クエリメソッド
Encore のデータベース API は、Go の標準 database/sql パッケージを踏襲しています。クエリ結果を変数に読み込むには .Scan() を使用します。
Query - 複数行
type User struct {
ID string
Email string
Name string
}
func listActiveUsers(ctx context.Context) ([]*User, error) {
rows, err := db.Query(ctx, `
SELECT id, email, name FROM users WHERE active = true
`)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Email, &u.Name); err != nil {
return nil, err
}
users = append(users, &u)
}
return users, rows.Err()
}
QueryRow - 単一行
func getUser(ctx context.Context, id string) (*User, error) {
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name FROM users WHERE id = $1
`, id).Scan(&u.ID, &u.Email, &u.Name)
if errors.Is(err, sqldb.ErrNoRows) {
return nil, &errs.Error{
Code: errs.NotFound,
Message: "user not found",
}
}
if err != nil {
return nil, err
}
return &u, nil
}
Exec - 戻り値なし
INSERT、UPDATE、DELETE 操作の場合:
func createUser(ctx context.Context, email, name string) error {
_, err := db.Exec(ctx, `
INSERT INTO users (id, email, name)
VALUES ($1, $2, $3)
`, generateID(), email, name)
return err
}
func updateUser(ctx context.Context, id, name string) error {
_, err := db.Exec(ctx, `
UPDATE users SET name = $1 WHERE id = $2
`, name, id)
return err
}
func deleteUser(ctx context.Context, id string) error {
_, err := db.Exec(ctx, `
DELETE FROM users WHERE id = $1
`, id)
return err
}
マイグレーション
ファイル構造
user/
└── migrations/
├── 1_create_users.up.sql
├── 2_add_posts.up.sql
└── 3_add_indexes.up.sql
命名規則
- 数字で始める (1、2 など)
- その後にアンダースコアと説明を続ける
.up.sqlで終わる- 数字は連番でなければならない
マイグレーションの例
-- migrations/1_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);
トランザクション
func transferFunds(ctx context.Context, fromID, toID string, amount int) error {
tx, err := db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback() // No-op if committed
_, err = tx.Exec(ctx, `
UPDATE accounts SET balance = balance - $1 WHERE id = $2
`, amount, fromID)
if err != nil {
return err
}
_, err = tx.Exec(ctx, `
UPDATE accounts SET balance = balance + $1 WHERE id = $2
`, amount, toID)
if err != nil {
return err
}
return tx.Commit()
}
Scan の使用
Scan メソッドは、クエリ結果のカラムを変数に読み込みます。カラムは名前ではなく位置でマッピングされます。Scan への引数の順序は、SELECT ステートメントのカラムの順序と一致している必要があります。
type User struct {
ID string
Email string
Name string
CreatedAt time.Time
}
// Single row with QueryRow
func getUser(ctx context.Context, id string) (*User, error) {
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name, created_at FROM users WHERE id = $1
`, id).Scan(&u.ID, &u.Email, &u.Name, &u.CreatedAt)
if err != nil {
return nil, err
}
return &u, nil
}
// You can also scan into an inline struct
func getItem(ctx context.Context, id int64) error {
var item struct {
ID int64
Title string
Done bool
}
err := db.QueryRow(ctx, `
SELECT id, title, done FROM items WHERE id = $1
`, id).Scan(&item.ID, &item.Title, &item.Done)
return err
}
SQL インジェクション対策
常にパラメータ化クエリを使用してください:
// SAFE - values are parameterized
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name FROM users WHERE email = $1
`, email).Scan(&u.ID, &u.Email, &u.Name)
// WRONG - SQL injection risk
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
エラーハンドリング
import (
"errors"
"encore.dev/storage/sqldb"
"encore.dev/beta/errs"
)
func getUser(ctx context.Context, id string) (*User, error) {
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name FROM users WHERE id = $1
`, id).Scan(&u.ID, &u.Email, &u.Name)
if errors.Is(err, sqldb.ErrNoRows) {
return nil, &errs.Error{
Code: errs.NotFound,
Message: "user not found",
}
}
if err != nil {
return nil, err
}
return &u, nil
}
ガイドライン
- 常にパラメータ化クエリ (
$1、$2など) を使用する - クエリ結果を読み込むには
Scanを使用する - カラムは位置でマッピングされる - 単一行を期待する場合は
sqldb.ErrNoRowsをチェックする - マイグレーションは起動時に自動的に適用される
- データベース名は小文字で、説明的にする
- 各サービスは通常、独自のデータベースを持つ
- アトミックでなければならない操作にはトランザクションを使用する
コメント
Markdown 対応