Database
Integrate Drizzle ORM with D1 databases in Ploy Start.
Database
Ploy Start provides built-in Drizzle ORM integration for D1 databases, giving you type-safe database queries with automatic schema inference.
Setup
1. Install Drizzle
pnpm add drizzle-orm2. Configure D1 Binding
Add a D1 database binding to your ploy.yaml:
kind: worker
build: pnpm build
out: dist
db:
DB: default3. Generate Types
Run the Ploy CLI to generate environment types:
pnpm typesThis creates env.d.ts with your D1 binding typed.
Schema Definition
Define your database schema using Drizzle:
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
email: text("email").notNull().unique(),
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
() => new Date(),
),
});
export const posts = sqliteTable("posts", {
id: integer("id").primaryKey({ autoIncrement: true }),
title: text("title").notNull(),
content: text("content"),
authorId: integer("author_id").notNull(),
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
() => new Date(),
),
});Using withDrizzle
The withDrizzle state factory adds a typed db instance to your handler context:
import { ploy, withDrizzle, z } from "@meetploy/start";
import { eq } from "drizzle-orm";
import type { Env } from "./env.js";
import * as schema from "./schema.js";
import { users } from "./schema.js";
const worker = ploy<Env>()
.state(withDrizzle("DB", schema))
.get(
"/users",
{
response: z.object({
users: z.array(
z.object({
id: z.number(),
name: z.string(),
email: z.string(),
}),
),
}),
},
async (ctx) => {
// ctx.state.db is typed with your schema
const allUsers = await ctx.state.db.select().from(users);
return { users: allUsers };
},
)
.build();
export default worker;The withDrizzle function takes two arguments: 1. The binding name (must
match your ploy.yaml db key) 2. Your schema object (optional, but
recommended for typed queries)
Query Examples
Select All
const allUsers = await ctx.state.db.select().from(users);Select with Where
import { eq } from "drizzle-orm";
const user = await ctx.state.db
.select()
.from(users)
.where(eq(users.id, 1))
.limit(1);Select Specific Columns
const userNames = await ctx.state.db.select({ name: users.name }).from(users);Insert
await ctx.state.db.insert(users).values({
name: "Alice",
email: "alice@example.com",
});Update
await ctx.state.db
.update(users)
.set({ name: "Alice Smith" })
.where(eq(users.id, 1));Delete
await ctx.state.db.delete(users).where(eq(users.id, 1));Raw D1 Access
For operations not supported by Drizzle, access D1 directly via ctx.env:
.get("/init", {
response: z.object({ success: z.boolean() })
}, async (ctx) => {
// Raw SQL execution
await ctx.env.DB.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
`);
return { success: true };
})Prepared Statements
const result = await ctx.env.DB.prepare("SELECT * FROM users WHERE id = ?")
.bind(userId)
.first();Batch Operations
const results = await ctx.env.DB.batch([
ctx.env.DB.prepare("INSERT INTO users (name, email) VALUES (?, ?)").bind(
"Alice",
"alice@example.com",
),
ctx.env.DB.prepare("INSERT INTO users (name, email) VALUES (?, ?)").bind(
"Bob",
"bob@example.com",
),
]);Using withRawD1
For raw D1 access as state, use withRawD1:
import { ploy, withRawD1 } from "@meetploy/start";
const worker = ploy<Env>()
.state(withRawD1("DB"))
.get("/users", {...}, async (ctx) => {
// ctx.state.d1.query, ctx.state.d1.first, ctx.state.d1.execute
const users = await ctx.state.d1.query("SELECT * FROM users");
return { users };
})
.build();Raw D1 Interface
interface RawD1 {
query<T>(sql: string, ...params: unknown[]): Promise<T[]>;
first<T>(sql: string, ...params: unknown[]): Promise<T | null>;
execute(sql: string, ...params: unknown[]): Promise<void>;
batch(
statements: { sql: string; params?: unknown[] }[],
): Promise<unknown[][]>;
}Full CRUD Example
import { ploy, withDrizzle, z } from "@meetploy/start";
import { eq } from "drizzle-orm";
import type { Env } from "./env.js";
import * as schema from "./schema.js";
import { users } from "./schema.js";
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string(),
});
const worker = ploy<Env>()
.state(withDrizzle("DB", schema))
// Initialize database
.get(
"/init",
{
response: z.object({ success: z.boolean() }),
},
async (ctx) => {
await ctx.env.DB.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at INTEGER
)
`);
return { success: true };
},
)
// List users
.get(
"/users",
{
response: z.object({ users: z.array(userSchema) }),
},
async (ctx) => {
const allUsers = await ctx.state.db.select().from(users);
return { users: allUsers };
},
)
// Get user by ID
.get(
"/users/:id",
{
params: z.object({ id: z.string() }),
response: z.object({ user: userSchema.nullable() }),
},
async (ctx) => {
const [user] = await ctx.state.db
.select()
.from(users)
.where(eq(users.id, parseInt(ctx.params.id)))
.limit(1);
return { user: user ?? null };
},
)
// Create user
.post(
"/users",
{
body: z.object({
name: z.string(),
email: z.string().email(),
}),
response: z.object({ user: userSchema }),
},
async (ctx) => {
const [user] = await ctx.state.db
.insert(users)
.values(ctx.body)
.returning();
return { user };
},
)
// Update user
.put(
"/users/:id",
{
params: z.object({ id: z.string() }),
body: z.object({
name: z.string().optional(),
email: z.string().email().optional(),
}),
response: z.object({ user: userSchema }),
},
async (ctx) => {
const [user] = await ctx.state.db
.update(users)
.set(ctx.body)
.where(eq(users.id, parseInt(ctx.params.id)))
.returning();
return { user };
},
)
// Delete user
.delete(
"/users/:id",
{
params: z.object({ id: z.string() }),
response: z.object({ success: z.boolean() }),
},
async (ctx) => {
await ctx.state.db
.delete(users)
.where(eq(users.id, parseInt(ctx.params.id)));
return { success: true };
},
)
.build();
export default worker;Best Practices
- Use migrations - Track schema changes with Drizzle migrations
- Index frequently queried columns - Add indexes for better performance
- Validate input - Use Zod schemas to validate data before inserting
- Handle errors - Wrap database operations in try/catch
- Use transactions - Group related operations when needed
How is this guide?
Last updated on