Quick Start
If you have not installed the package yet, see the Installation guide.
This guide walks through defining a model, writing a migration, and performing all five core CRUD operations — no Cloudflare console visit required.
1. Define a Model
Create a TypeScript file for your model. Start with an attributes interface that mirrors your database columns, then extend BaseModel:
// src/models/User.ts
import { BaseModel } from '@orphnet/d1-eloquent'
interface UserAttrs {
id: string
name: string
email: string
created_at?: string
updated_at?: string
}
export class User extends BaseModel<UserAttrs> {
static table = 'users'
static primaryKey = 'id'
}BaseModel<TAttrs> gives you create, find, query, set, save, and delete without any additional setup.
Primary keys are TEXT
d1-eloquent uses TEXT primary keys (UUIDs) by default. Use crypto.randomUUID() when creating records — see the create example below.
2. Generate a Migration
Use the CLI to scaffold a migration file:
npx @orphnet/d1-eloquent make:migration create_users_tableThis creates src/database/migrations/TIMESTAMP_create_users_table.ts with commented schema stubs.
3. Write the Migration
Open the generated file and define your table using the schema builder:
// src/database/migrations/20260101000000_create_users_table.ts
import type { TMigration } from '@orphnet/d1-eloquent/cli'
import { Schema } from '@orphnet/d1-eloquent/cli'
const migration: TMigration = {
name: '20260101000000_create_users_table',
up: (schema: Schema) => {
schema.createTable('users', (t) => {
t.id()
t.text('name')
t.text('email', { unique: true })
t.timestamps()
})
},
down: (schema: Schema) => {
schema.dropTable('users')
},
}
export default migrationSee the Schema Builder reference for all column types and options.
4. Run the Migration
Apply the migration to your local D1 database:
npx @orphnet/d1-eloquent migrate --db DB --localYou should see output confirming the migration ran:
Running: 20260101000000_create_users_table
✓ Applied 1 migration5. CRUD Operations
With the table in place, your Worker can create and query records. Here are all five core operations:
Create
import { User } from './models/User'
const user = await User.create(env.DB, {
id: crypto.randomUUID(),
name: 'Alice',
email: 'alice@example.com',
})
console.log(user.attrs.id) // "550e8400-e29b-41d4-a716-446655440000"
console.log(user.attrs.name) // "Alice"Find by Primary Key
const user = await User.find(env.DB, '550e8400-e29b-41d4-a716-446655440000')
if (user) {
console.log(user.attrs.email) // "alice@example.com"
} else {
console.log('Not found')
}Query with Filters
const user = await User.query()
.whereEq('email', 'alice@example.com')
.first(env.DB)
// List all users whose name starts with "Ali"
const users = await User.query()
.whereLike('name', 'Ali%')
.orderBy('name', 'asc')
.limit(20)
.get(env.DB)Update
const user = await User.find(env.DB, userId)
if (user) {
user.set({ name: 'Alice Smith' })
await user.save(env.DB)
}set() merges the provided attributes onto the model instance. save() issues an UPDATE for only the changed fields.
Delete
const user = await User.find(env.DB, userId)
if (user) {
await user.delete(env.DB)
}By default, delete() issues a permanent DELETE FROM users WHERE id = ?. To soft-delete instead (set deleted_at and leave the row intact), see Soft Deletes.
Complete Worker Example
Here is a minimal Hono worker that wires everything together:
import { Hono } from 'hono'
import { User } from './models/User'
type Env = { Bindings: { DB: D1Database } }
const app = new Hono<Env>()
app.get('/users', async (c) => {
const users = await User.query().limit(50).get(c.env.DB)
return c.json(users.map(u => u.attrs))
})
app.post('/users', async (c) => {
const { name, email } = await c.req.json<{ name: string; email: string }>()
const user = await User.create(c.env.DB, {
id: crypto.randomUUID(),
name,
email,
})
return c.json(user.attrs, 201)
})
app.delete('/users/:id', async (c) => {
const user = await User.find(c.env.DB, c.req.param('id'))
if (!user) return c.notFound()
await user.delete(c.env.DB)
return c.body(null, 204)
})
export default appNext Steps
- Soft Deletes — set
deleted_atinstead of removing rows permanently; supportsrestore(),withTrashed(), andonlyTrashed()query scopes - Revision Tracking — immutable audit log of every change; time-travel with
asOf()andrevertTo() - Seeders & Factories — populate local databases with realistic test data
- API Reference — full method signatures and options for
BaseModel