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.
npx skills add encoredev/skills --skill encore-database -a claude-code
npx skills add encoredev/skills --skill database -a claude-codeWhat 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
queryRowandexec - 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-codenpx skills add encoredev/skills --skill database -a claude-code2Manually: put the files in place
~/.claude/skills/database/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/database/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill database -a codex2Manually: put the files in place
.agents/skills/database/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill database -a cursor2Manually: put the files in place
.cursor/skills/database/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install encoredev/skills database2Manually: put the files in place
.github/skills/database/SKILL.md1Skills CLI (Vercel)
npx skills add encoredev/skills --skill database -a gemini-cli2Platform CLI
gemini skills install https://github.com/encoredev/skills --path encore/databaseManually: put the files in place
.devin/skills/database/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add encoredev/skills --skill database -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
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