Sequelize is a mature ORM for SQL databases with model definitions, associations, transactions, and broad driver support. It is a strong fit when your team prefers an Active Record style API and deploys DaloyJS on Node.js.
One request through Sequelize
01clientHTTP requestGET /users/:id
02zodValidated inputparams.id is a uuid
03sequelizeModel querystate.db.User.findByPk(id)
04responseTyped bodyuser.toJSON() | 404
Zod validates the request, the handler queries a Sequelize model off state.db, then the response schema checks the body on the way out (call toJSON() so the plain object matches the schema).
Swap the driver package if you target MySQL, MariaDB, MSSQL, or SQLite instead of Postgres.
This guide targets Sequelize v6, the current stable line (sequelize@latest is 6.37 as of October 2026). Sequelize v7 ships under a new package name, @sequelize/core, and is still in alpha, so its APIs may change before a stable release.
2. Define a model
The scaffolded tsconfig.json sets verbatimModuleSyntax, and Node's type stripping (what daloy dev runs) needs type-only imports marked as such, so import the attribute helpers with import type:
// src/db/plugin.tsimport type { App } from "@daloyjs/core";import { sequelize, User } from "./sequelize.ts";export const db = { sequelize, User };// Call with the root app, not through app.register(): a decoration made// inside a registered plugin is visible only to that plugin's own routes.export async function sequelizePlugin(app: App) { await sequelize.authenticate(); app.decorate("db", db); app.onClose(async () => { await sequelize.close(); });}
4. Augment app state types
Add the declare module block to the same module that exports db, not to a separate .d.ts file. Declaration files are exempt from type-checking when skipLibCheck is on (the scaffolded default), so a broken import inside a .d.ts fails silently and state.db degrades to any.
ts
// src/db/plugin.ts (same module as the plugin above)declare module "@daloyjs/core" { interface AppState { db: typeof db; }}
5. Use it in routes
ts
// src/server.tsimport { z } from "zod";import { App, NotFoundError } from "@daloyjs/core";import { serve } from "@daloyjs/core/node";import { sequelizePlugin } from "./db/plugin.ts";const UserSchema = z.object({ id: z.uuid(), email: z.email(), name: z.string().nullable(),});const app = new App();await sequelizePlugin(app);app.get( "/users/:id", { operationId: "getUser", request: { params: z.object({ id: z.uuid() }) }, responses: { 200: { description: "Found", body: UserSchema }, 404: { description: "Not found" }, }, }, async ({ params, state }) => { const user = await state.db.User.findByPk(params.id); if (!user) throw new NotFoundError("User not found"); return { status: 200, body: user.toJSON() }; },);await app.ready();serve(app, { port: 3000 });
Transactions
Use managed transactions so DaloyJS can map one handler invocation to one atomic unit of work. This example assumes Order and Inventory models defined like User above, exported on db, and a route that declares 201.
Sequelize supports migrations via the CLI, but many teams keep model definitions in TypeScript and run explicit migration files through sequelize-cli or Umzug. Run migrations as a deploy step, never at app boot (see migrations), and never call sequelize.sync() in production.
sequelize-cli is CommonJS-first. In a scaffolded DaloyJS app ("type": "module"), keep .sequelizerc and the config file it points to in CommonJS, for example config/config.cjs, and check the CLI docs for your version before writing migrations as ES modules.
Runtime constraints
Sequelize depends on Node-oriented drivers, so it is best on the Node.js adapter. onClose() runs on the Node.js, Bun, and Deno adapters only; on Lambda, Vercel, or Azure Functions keep the pool small (pool: { max: 1 }) and see Serverless and edge lifecycle. For edge runtimes, prefer Drizzle, Prisma with driver adapters, or Supabase.