encore-database

Par encoredev · skills

Requêtes de base de données, migrations et intégration ORM avec Encore.ts.

npx skills add https://github.com/encoredev/skills --skill encore-database

Opérations de base de données Encore

Instructions

Configuration de la base de données

import { SQLDatabase } from "encore.dev/storage/sqldb";

const db = new SQLDatabase("mydb", {
  migrations: "./migrations",
});

Méthodes de requête

Encore fournit plusieurs méthodes de requête :

query - Plusieurs lignes

Retourne un itérateur asynchrone pour plusieurs lignes :

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 - Toutes les lignes en tant que tableau

Retourne toutes les lignes sous forme de tableau (wrapper de commodité autour de query) :

const users = await db.queryAll<User>`
  SELECT id, email, name FROM users WHERE active = true
`;
// users est User[]

queryRow - Ligne unique

Retourne une ligne ou 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 - Aucune valeur de retour

Pour les opérations 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}
`;

Méthodes de requête brute

Utilisez des chaînes SQL brutes avec des paramètres positionnels ($1, $2, etc.) au lieu de littéraux template :

// Requête brute retournant plusieurs lignes
const rows = await db.rawQuery<User>("SELECT * FROM users WHERE active = $1", true);

// Requête brute retournant une seule ligne
const user = await db.rawQueryRow<User>("SELECT * FROM users WHERE id = $1", userId);

// Requête brute retournant toutes les lignes en tant que tableau
const users = await db.rawQueryAll<User>("SELECT * FROM users WHERE role = $1", "admin");

// Exec brut pour INSERT/UPDATE/DELETE
await db.rawExec("INSERT INTO users (id, email) VALUES ($1, $2)", id, email);

Partage de base de données entre services

Référencez une base de données possédée par un autre service en utilisant SQLDatabase.named() :

import { SQLDatabase } from "encore.dev/storage/sqldb";

// Dans le service qui possède la base de données
const db = new SQLDatabase("shared-db", {
  migrations: "./migrations",
});

// Dans un autre service qui a besoin d'accès
const sharedDb = SQLDatabase.named("shared-db");

// Maintenant vous pouvez interroger la base de données partagée
const user = await sharedDb.queryRow<User>`SELECT * FROM users WHERE id = ${id}`;

Migrations

Structure des fichiers

service/
└── migrations/
    ├── 001_create_users.up.sql
    ├── 002_add_posts.up.sql
    └── 003_add_indexes.up.sql

Convention de nommage

  • Commencez par un numéro (001, 002, etc.)
  • Suivi d'un tiret bas et d'une description
  • Terminez par .up.sql
  • Les numéros doivent être séquentiels

Exemple de migration

-- 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);

Intégration de Drizzle ORM

Configuration

// 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);

Schéma

// 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(),
});

Configuration Drizzle

// drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  out: "migrations",
  schema: "schema.ts",
  dialect: "postgresql",
});

Générer les migrations : drizzle-kit generate

Utiliser 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));

Protection contre l'injection SQL

Les littéraux template d'Encore échappent automatiquement les valeurs :

// SÛR - les valeurs sont paramétrées
const email = "user@example.com";
await db.queryRow`SELECT * FROM users WHERE email = ${email}`;

// MAUVAIS - risque d'injection SQL
await db.queryRow(`SELECT * FROM users WHERE email = '${email}'`);

Recommandations

  • Utilisez toujours les littéraux template pour les requêtes (échappement automatique)
  • Spécifiez les types avec les génériques : query<User>, queryRow<User>
  • Les migrations sont appliquées automatiquement au démarrage
  • Utilisez queryRow quand vous attendez 0 ou 1 résultat
  • Utilisez query avec itération asynchrone pour plusieurs lignes
  • Les noms de base de données doivent être minuscules et explicites
  • Chaque service a généralement sa propre base de données

Skills similaires