Search docs

Jump between documentation pages.

Using SQL ORMs with DaloyJS

DaloyJS is database-agnostic. Any SQL client that runs on your target runtime works, so pick the ORM or query layer that fits your team. The framework gives you two primitives that make integration boring (in a good way):

  • app.decorate("db", client) attaches a shared client to every handler's state.
  • app.onClose(async () => client.disconnect()) ties cleanup to graceful shutdown.

The recommended pattern

Wrap the database client in a plugin and register it once at the root of your app. Handlers read it from state with full type-safety.

ts
// src/db/plugin.ts
import type { App } from "@daloyjs/core";

export function databasePlugin(client: DbClient) {
  return {
    name: "database",
    async register(app: App) {
      app.decorate("db", client);
      app.onClose(async () => {
        await client.$disconnect?.();
      });
    },
  };
}

// src/server.ts
const app = new App();
app.register(databasePlugin(await createClient()));

app.route({
  method: "GET",
  path: "/users/:id",
  operationId: "getUser",
  request: { params: z.object({ id: z.string().uuid() }) },
  responses: { 200: { description: "ok", body: UserSchema } },
  handler: async ({ params, state }) => {
    const user = await state.db.user.findUnique({ where: { id: params.id } });
    return user
      ? { status: 200, body: user }
      : { status: 404, body: { type: "about:blank", title: "Not found", status: 404 } };
  },
});

Pick your ORM

  • Prisma — schema-first, mature migrations, great DX.
  • Drizzle ORM — TypeScript-first, edge-friendly, SQL-like API.
  • TypeORM — decorator-based entities for object-oriented teams.
  • Sequelize — mature Active Record style models with broad SQL dialect support.

Need a platform client instead?

Supabase is not an ORM. It is a hosted Postgres platform with a fetch-based JavaScript client, auth, storage, realtime, and edge-friendly APIs. If that is the shape you need, use Supabase with DaloyJS.

  • Supabase — platform client for hosted Postgres + auth via @supabase/supabase-js.

Keep ORM and ODM separate

This section is intentionally SQL-focused. If you are using MongoDB or Couchbase, jump to the ODM overview and use Mongooseor Ottoman instead of forcing document models into an ORM-shaped abstraction.

Runtime compatibility cheat sheet

Data layerNode.jsBunDenoCloudflare WorkersVercel Edge
PrismaYesYesYesYes, with Driver AdaptersYes, with Driver Adapters
Drizzle ORMYesYesYesYesYes
TypeORMYesPartialPartialNoNo
SequelizeYesPartialNoNoNo
Supabase JSYesYesYesYesYes

For edge runtimes (Cloudflare Workers, Vercel Edge), prefer Drizzle or Supabase, or use Prisma with Driver Adapters. TypeORM and Sequelize both rely on Node-centric drivers and are best on the Node.js adapter.

Typing the decorated client

Use the exported AppState augmentation point to make decorated clients available on state in every handler:

ts
// src/types/state.d.ts
import type { PrismaClient } from "@prisma/client";

declare module "@daloyjs/core" {
  interface AppState {
    db: PrismaClient;
  }
}

Transactions

Don't open transactions in middleware. Open them inside the handler that owns the unit of work, so your contract response (success or error) maps cleanly onto commit / rollback.

ts
handler: async ({ body, state }) => {
  return state.db.$transaction(async (tx) => {
    const order = await tx.order.create({ data: body });
    await tx.inventory.update({
      where: { sku: body.sku },
      data: { stock: { decrement: body.qty } },
    });
    return { status: 201, body: order };
  });
}

Errors

Translate database errors into framework errors so they serialize as problem+json automatically:

ts
import { HttpError } from "@daloyjs/core";

try {
  return await state.db.user.create({ data: body });
} catch (err) {
  if (isUniqueViolation(err)) {
    throw new HttpError(409, {
      title: "User already exists",
      type: "https://daloyjs.dev/errors/duplicate",
    });
  }
  throw err;
}

Next steps