Use Sequelize with DaloyJS
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.
1. Install
pnpm add sequelize pg pg-hstore
pnpm add -D @types/validator @types/node typescriptSwap the driver package if you target MySQL, MariaDB, MSSQL, or SQLite instead of Postgres.
2. Define a model
// src/db/sequelize.ts
import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional } from "sequelize";
export const sequelize = new Sequelize(process.env.DATABASE_URL!, {
dialect: "postgres",
logging: process.env.NODE_ENV === "production" ? false : console.log,
});
export class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
declare id: CreationOptional<string>;
declare email: string;
declare name: string | null;
}
User.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
name: {
type: DataTypes.STRING,
allowNull: true,
},
},
{
sequelize,
modelName: "User",
tableName: "users",
underscored: true,
}
);3. Create a Sequelize plugin
// src/db/plugin.ts
import type { App } from "@daloyjs/core";
import { sequelize, User } from "./sequelize";
export const db = { sequelize, User };
export const sequelizePlugin = {
name: "sequelize",
async register(app: App) {
await sequelize.authenticate();
app.decorate("db", db);
app.onClose(async () => {
await sequelize.close();
});
},
};4. Augment app state types
// src/types/state.d.ts
import type { db } from "../db/plugin";
declare module "@daloyjs/core" {
interface AppState {
db: typeof db;
}
}5. Use it in routes
// src/server.ts
import { z } from "zod";
import { App, HttpError } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";
import { sequelizePlugin } from "./db/plugin";
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().nullable(),
});
const app = new App();
app.register(sequelizePlugin);
app.route({
method: "GET",
path: "/users/:id",
operationId: "getUser",
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
200: { description: "Found", body: UserSchema },
404: { description: "Not found" },
},
handler: async ({ params, state }) => {
const user = await state.db.User.findByPk(params.id);
if (!user) {
throw new HttpError(404, { title: "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.
handler: async ({ body, state }) => {
const created = await state.db.sequelize.transaction(async (transaction) => {
const order = await state.db.Order.create(body, { transaction });
await state.db.Inventory.decrement("stock", {
by: body.qty,
where: { sku: body.sku },
transaction,
});
return order;
});
return { status: 201, body: created.toJSON() };
}Migrations
Sequelize supports migrations via the CLI, but many teams keep model definitions in TypeScript and run explicit migration files through sequelize-cli or Umzug. Keep that workflow outside your request path and initialize models before calling app.ready().
pnpm add -D sequelize-cli
pnpm sequelize-cli migration:generate --name create-users
pnpm sequelize-cli db:migrateRuntime constraints
Sequelize depends on Node-oriented drivers, so it is best on the Node.js adapter. For edge runtimes, prefer Drizzle, Prisma with Driver Adapters, or Supabase.
Compare with Prisma, Drizzle, TypeORM, or the ODM overview if you are working with document databases.