mcpField
E
encore-go-database

encore-go-database

github.com/encoredevデータ・データベース30567 回インストール2 日前に更新
リポジトリ

encore-go-database について

EncoreのGo SQLデータベースAPIを使うためのリファレンススキルです。Go開発者向けに、クエリ、マイグレーション、トランザクション、パラメーター化クエリの扱いを説明します。

主な機能

  1. 01sqldbによるデータベース設定
  2. 02Query、QueryRow、Execの使い方
  3. 03マイグレーションの命名と構成
  4. 04トランザクションとScanの指針
  5. 05パラメーター化クエリとエラー処理

活用シーン

  1. 01Encore Goサービスのデータベースアクセス実装
  2. 02SQLマイグレーションの追加と整理
  3. 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 をチェックする
  • マイグレーションは起動時に自動的に適用される
  • データベース名は小文字で、説明的にする
  • 各サービスは通常、独自のデータベースを持つ
  • アトミックでなければならない操作にはトランザクションを使用する

コメント