Skip to main content
skill CX Codex

encore-database — Claude Code skill: what it does, how to install

Teaches the AI agent to manage PostgreSQL in Encore.ts applications. It guides schema migrations and typed SQL queries using SQLDatabase.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add encoredev/skills --skill encore-database -a claude-code
npx skills add encoredev/skills --skill database -a claude-code
Open on GitHub

What encore-database does

This skill guides the assistant on utilizing SQLDatabase from encore.dev/storage/sqldb. It enforces typed database operations with methods such as query, queryRow, and queryAll. Write queries rely on exec. Template literals automatically parameterize variables to block SQL injection vulnerabilities.

Database migrations go into the migrations/ directory using .up.sql extensions with strict sequential numbers. Without ordered numbering, startup migrations fail. The instructions also explain cross-service database references using SQLDatabase.named() and Drizzle ORM integration powered by db.connectionString.

What it helps with

  • create sequential SQL migration files in Encore.ts
  • write parameterized queries using queryRow and exec
  • connect Drizzle ORM to PostgreSQL via db.connectionString
  • share an existing database with another service using SQLDatabase.named

Example prompts for the agent

  • Create a migration file for user sessions and add an insert query with db.exec
  • Fetch an active account by id using queryRow in Encore.ts
  • Configure Drizzle ORM for my Encore database schema

How to install encore-database

1Skills CLI (Vercel)

npx skills add encoredev/skills --skill encore-database -a claude-code
npx skills add encoredev/skills --skill database -a claude-code

2Manually: put the files in place

~/.claude/skills/database/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/database/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about encore-database

How to install encore-database in Claude Code?

Run npx skills add encoredev/skills --skill encore-database -a claude-code in your terminal. This configures the database skill for your agent.

What does the encore-database skill do?

It instructs the agent on handling PostgreSQL storage inside Encore.ts backend services. It covers migrations, raw queries, async iterators, and ORM setups.

How does the skill handle SQL injection security?

It directs the AI to use template literals for SQL statements, ensuring parameters are safely escaped. For raw SQL calls, it enforces positional placeholders like $1 and $2.

Source text

SKILL.md · Apache-2.0
---
name: encore-database
description: Work with PostgreSQL in Encore.ts using `SQLDatabase` from `encore.dev/storage/sqldb` — schema migrations and SQL queries.
when_to_use: >-
  User wants to add a database table, write a migration, run a SQL query, insert/update/delete rows, set up Drizzle or Prisma against an Encore database, or design a relational schema. Covers `new SQLDatabase(...)`, `db.query`, `db.queryRow`, `db.exec`, the `migrations/` directory, `*.up.sql` files, sequential migration numbering, and ORM integration via `db.connectionString`. Trigger phrases: "Postgres table", "user_sessions table", "SQL", "migration", "queryRow", "INSERT", "SELECT", "schema", "Drizzle", "Prisma".
---

# Encore Database Operations

## Instructions

### Database Setup

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

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

## Query Methods

Encore provides several query methods:

### `query` - Multiple Rows

Returns an async iterator for multiple rows:

```typescript
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` - All Rows as Array

Returns all rows as an array (convenience wrapper around `query`):

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

### `queryRow` - Single Row

Returns one row or null:

```typescript
const user = await db.queryRow<User>`
  SELECT id, email, name FROM users WHERE id = ${userId}
`;

if (!user) {
  throw APIError.notFound("user not found");
}
```

### `exec` - No Return Value

For INSERT, UPDATE, DELETE operations:

```typescript
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}
`;
```

### Raw Query Methods

Use raw SQL strings with positional parameters (`$1`, `$2`, etc.) instead of template literals:

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

## Database Sharing Across Services

Reference a database owned by another service using `SQLDatabase.named()`:

```typescript
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}`;
```

## Migrations

### File Structure

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

### Naming Convention

- Start with a number (001, 002, etc.)
- Followed by underscore and description
- End with `.up.sql`
- Numbers must be sequential

### Example Migration

```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 Integration

### Setup

```typescript
// 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

```typescript
// 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 Config

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

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

Generate migrations: `drizzle-kit generate`

### Using Drizzle

```typescript
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 Injection Protection

Encore's template literals automatically escape values:

```typescript
// 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}'`);
```

## Guidelines

- Always use template literals for queries (automatic escaping)
- Specify types with generics: `query<User>`, `queryRow<User>`
- Migrations are applied automatically on startup
- Use `queryRow` when expecting 0 or 1 result
- Use `query` with async iteration for multiple rows
- Database names should be lowercase, descriptive
- Each service typically has its own database

© encoredev · Apache-2.0 · encoredev/skills

Similar skills

All skills for Data and databases →

From the same set25

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor