Skip to content

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:

ts
// 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:

sh
npx @orphnet/d1-eloquent make:migration create_users_table

This 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:

ts
// 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 migration

See the Schema Builder reference for all column types and options.

4. Run the Migration

Apply the migration to your local D1 database:

sh
npx @orphnet/d1-eloquent migrate --db DB --local

You should see output confirming the migration ran:

Running: 20260101000000_create_users_table
✓ Applied 1 migration

5. CRUD Operations

With the table in place, your Worker can create and query records. Here are all five core operations:

Create

ts
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

ts
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

ts
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

ts
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

ts
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:

ts
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 app

Next Steps

  • Soft Deletes — set deleted_at instead of removing rows permanently; supports restore(), withTrashed(), and onlyTrashed() query scopes
  • Revision Tracking — immutable audit log of every change; time-travel with asOf() and revertTo()
  • Seeders & Factories — populate local databases with realistic test data
  • API Reference — full method signatures and options for BaseModel

Released under the MIT License.