Search docs

Jump between documentation pages.

Use TypeORM with DaloyJS

TypeORM gives you decorator-based entities and the active-record / data-mapper patterns familiar to Java and .NET teams. It runs best on the Node.js adapter.

1. Install

ts
pnpm add typeorm reflect-metadata pg
pnpm add -D @types/node

TypeORM relies on reflect-metadata and decorator metadata. Make sure your tsconfig.json enables them:

ts
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false
  }
}

2. Define an entity

ts
// src/db/entities/User.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from "typeorm";

@Entity("users")
export class User {
  @PrimaryGeneratedColumn("uuid")
  id!: string;

  @Column({ unique: true })
  email!: string;

  @Column({ type: "text", nullable: true })
  name!: string | null;

  @CreateDateColumn({ name: "created_at" })
  createdAt!: Date;
}

3. Configure the DataSource

ts
// src/db/data-source.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { User } from "./entities/User";

export const AppDataSource = new DataSource({
  type: "postgres",
  url: process.env.DATABASE_URL,
  entities: [User],
  migrations: ["src/db/migrations/*.ts"],
  synchronize: false, // use migrations in production
  logging: process.env.NODE_ENV !== "production",
});

4. Create a TypeORM plugin

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

export const typeormPlugin = {
  name: "typeorm",
  async register(app: App) {
    if (!AppDataSource.isInitialized) {
      await AppDataSource.initialize();
    }
    app.decorate("db", AppDataSource);
    app.onClose(async () => {
      if (AppDataSource.isInitialized) await AppDataSource.destroy();
    });
  },
};

5. Augment app state types

ts
// src/types/state.d.ts
import type { DataSource } from "typeorm";

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

6. Use repositories in routes

ts
// src/server.ts
import "reflect-metadata";
import { z } from "zod";
import { App } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";
import { typeormPlugin } from "./db/plugin";
import { User } from "./db/entities/User";

const app = new App();
app.register(typeormPlugin);

const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string().nullable(),
  createdAt: z.coerce.date(),
});

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 repo = state.db.getRepository(User);
    const user = await repo.findOneBy({ id: params.id });
    return user
      ? { status: 200, body: user }
      : { status: 404, body: { type: "about:blank", title: "Not found", status: 404 } };
  },
});

app.route({
  method: "POST",
  path: "/users",
  operationId: "createUser",
  request: { body: z.object({ email: z.string().email(), name: z.string().optional() }) },
  responses: { 201: { description: "Created", body: UserSchema } },
  handler: async ({ body, state }) => {
    const repo = state.db.getRepository(User);
    const created = await repo.save(repo.create(body));
    return { status: 201, body: created };
  },
});

await app.ready();
serve(app, { port: 3000 });

Transactions

ts
handler: async ({ body, state }) => {
  return state.db.transaction(async (manager) => {
    const order = await manager.save(Order, manager.create(Order, body));
    await manager.decrement(Inventory, { sku: body.sku }, "stock", body.qty);
    return { status: 201, body: order };
  });
}

Migrations

ts
pnpm typeorm migration:generate src/db/migrations/InitUser -d src/db/data-source.ts
pnpm typeorm migration:run -d src/db/data-source.ts

Runtime notes

  • TypeORM uses Node-only APIs (filesystem, native drivers). It does not run on Cloudflare Workers or Vercel Edge — use Drizzle or Supabase there.
  • On Bun and Deno, prefer drizzle-style postgres clients unless you need TypeORM's decorators.
  • Always import reflect-metadata once at the entrypoint, before anything else.

Compare with Prisma, Drizzle, Sequelize, or the ODM overview if you need document models.