ORM Integration
ORM Integration
Section titled “ORM Integration”Veloce-TS provides a built-in ORM layer with first-class support for Drizzle ORM, Prisma, and TypeORM, plus a dependency injection bridge so you can inject your database anywhere in the application.
Table of Contents
Section titled “Table of Contents”- Drizzle ORM Integration
- Prisma Integration
- TypeORM Integration
- Base Repository Pattern
- Transactions
- Best Practices
Drizzle ORM Integration
Section titled “Drizzle ORM Integration”Installation
Section titled “Installation”bun add drizzle-orm# SQLite (Bun native)bun add drizzle-orm @libsql/client# PostgreSQLbun add drizzle-orm postgres# MySQLbun add drizzle-orm mysql2Registering Drizzle with the DI Container
Section titled “Registering Drizzle with the DI Container”Use registerDrizzle to make your database instance available everywhere via dependency injection:
import { VeloceTS, registerDrizzle } from 'veloce-ts';import { drizzle } from 'drizzle-orm/bun-sqlite';import { Database } from 'bun:sqlite';
const sqlite = new Database('app.db');const db = drizzle(sqlite);
const app = new VeloceTS({ title: 'My API', version: '1.0.0',});
// Register before compilingregisterDrizzle(app, db);
app.include(ProductController);await app.compile();app.listen(3000);Injecting the Database with @InjectDB
Section titled “Injecting the Database with @InjectDB”import { Controller, Get, Post, Body, Param, HttpCode, InjectDB } from 'veloce-ts';import { eq } from 'drizzle-orm';import { products } from './schema';import { z } from 'zod';
const CreateProductSchema = z.object({ name: z.string().min(2), price: z.number().positive(), stock: z.number().int().min(0).default(0),});
@Controller('/products')class ProductController { @Get('/') async list(@InjectDB() db: ReturnType<typeof drizzle>) { return await db.select().from(products); }
@Get('/:id') async getById( @Param('id') id: string, @InjectDB() db: ReturnType<typeof drizzle> ) { const result = await db .select() .from(products) .where(eq(products.id, parseInt(id)));
if (!result.length) { throw new NotFoundException('Product not found'); }
return result[0]; }
@Post('/') @HttpCode(201) async create( @Body(CreateProductSchema) data: z.infer<typeof CreateProductSchema>, @InjectDB() db: ReturnType<typeof drizzle> ) { const inserted = await db .insert(products) .values(data) .returning();
return inserted[0]; }}Custom Injection Token
Section titled “Custom Injection Token”If you need multiple databases (e.g. read replica + write primary), use custom tokens:
import { registerDrizzle, InjectDB, DB_TOKEN } from 'veloce-ts';
const WRITE_DB = Symbol('WRITE_DB');const READ_DB = Symbol('READ_DB');
registerDrizzle(app, primaryDb, WRITE_DB);registerDrizzle(app, replicaDb, READ_DB);
@Get('/')async list( @InjectDB(READ_DB) readDb: any,) { return await readDb.select().from(products);}
@Post('/')async create( @Body(CreateProductSchema) data: any, @InjectDB(WRITE_DB) writeDb: any,) { return await writeDb.insert(products).values(data).returning();}Defining a Schema
Section titled “Defining a Schema”import { sqliteTable, integer, text, real } from 'drizzle-orm/sqlite-core';
export const products = sqliteTable('products', { id: integer('id').primaryKey({ autoIncrement: true }), name: text('name').notNull(), price: real('price').notNull(), stock: integer('stock').notNull().default(0), createdAt: text('created_at').default(new Date().toISOString()),});
export const users = sqliteTable('users', { id: integer('id').primaryKey({ autoIncrement: true }), email: text('email').notNull().unique(), name: text('name').notNull(), passwordHash: text('password_hash').notNull(),});Prisma Integration
Section titled “Prisma Integration”Installation
Section titled “Installation”bun add @prisma/clientbunx prisma initimport { VeloceTS } from 'veloce-ts';import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();const DB_TOKEN = Symbol('PRISMA_DB');
const app = new VeloceTS();
// Register Prisma using the generic DI containerapp.getContainer().registerFactory(DB_TOKEN, () => prisma, { scope: 'singleton' });
app.include(UserController);await app.compile();app.listen(3000);import { Controller, Get, Post, Body, Param, Depends, HttpCode } from 'veloce-ts';import { PrismaClient } from '@prisma/client';
const DB_TOKEN = Symbol('PRISMA_DB');
@Controller('/users')class UserController { @Get('/') async list(@Depends(DB_TOKEN) db: PrismaClient) { return await db.user.findMany(); }
@Post('/') @HttpCode(201) async create( @Body(CreateUserSchema) data: any, @Depends(DB_TOKEN) db: PrismaClient ) { return await db.user.create({ data }); }}TypeORM Integration
Section titled “TypeORM Integration”Installation
Section titled “Installation”bun add typeorm reflect-metadataimport { VeloceTS } from 'veloce-ts';import { DataSource } from 'typeorm';import { User } from './entities/user.entity';
const dataSource = new DataSource({ type: 'sqlite', database: 'app.db', entities: [User], synchronize: true,});
await dataSource.initialize();
const app = new VeloceTS();const DS_TOKEN = Symbol('DATA_SOURCE');app.getContainer().registerFactory(DS_TOKEN, () => dataSource, { scope: 'singleton' });
app.include(UserController);await app.compile();app.listen(3000);Base Repository Pattern
Section titled “Base Repository Pattern”For a cleaner architecture, create repository classes and inject them via the standard DI container:
import { Injectable } from 'veloce-ts';import { eq } from 'drizzle-orm';import { products } from '../schema';
@Injectable()class ProductRepository { constructor( @InjectDB() private db: ReturnType<typeof drizzle> ) {}
async findAll() { return await this.db.select().from(products); }
async findById(id: number) { const result = await this.db .select() .from(products) .where(eq(products.id, id)); return result[0] ?? null; }
async create(data: typeof products.$inferInsert) { const inserted = await this.db .insert(products) .values(data) .returning(); return inserted[0]; }
async update(id: number, data: Partial<typeof products.$inferInsert>) { const updated = await this.db .update(products) .set(data) .where(eq(products.id, id)) .returning(); return updated[0] ?? null; }
async delete(id: number) { const deleted = await this.db .delete(products) .where(eq(products.id, id)) .returning(); return deleted.length > 0; }}@Controller('/products')class ProductController { @Get('/') async list(@Depends(ProductRepository) repo: ProductRepository) { return await repo.findAll(); }
@Get('/:id') async getById( @Param('id') id: string, @Depends(ProductRepository) repo: ProductRepository ) { const product = await repo.findById(parseInt(id)); if (!product) throw new NotFoundException('Product not found'); return product; }
@Post('/') @HttpCode(201) async create( @Body(CreateProductSchema) data: any, @Depends(ProductRepository) repo: ProductRepository ) { return await repo.create(data); }}Transactions
Section titled “Transactions”Drizzle Transactions
Section titled “Drizzle Transactions”import { InjectDB } from 'veloce-ts';
@Post('/transfer')async transfer( @Body(TransferSchema) data: any, @InjectDB() db: ReturnType<typeof drizzle>) { return await db.transaction(async (tx) => { // Deduct from sender await tx .update(accounts) .set({ balance: sql`balance - ${data.amount}` }) .where(eq(accounts.id, data.fromId));
// Add to receiver await tx .update(accounts) .set({ balance: sql`balance + ${data.amount}` }) .where(eq(accounts.id, data.toId));
return { success: true, amount: data.amount }; });}Best Practices
Section titled “Best Practices”1. Register the DB before compiling
Section titled “1. Register the DB before compiling”// ✓ Correct orderregisterDrizzle(app, db);app.include(ProductController);await app.compile(); // <-- always awaitapp.listen(3000);
// ✗ Wrong - registering after compileawait app.compile();registerDrizzle(app, db); // too late2. Use a single DB instance (singleton)
Section titled “2. Use a single DB instance (singleton)”registerDrizzle registers as a singleton by default. Don’t create new connections per request.
3. Keep business logic in repositories or services
Section titled “3. Keep business logic in repositories or services”// ✓ Good - thin controller, logic in repository@Get('/:id')async get(@Param('id') id: string, @Depends(ProductRepo) repo: ProductRepo) { return await repo.findById(parseInt(id));}
// ✗ Bad - SQL directly in controller@Get('/:id')async get(@Param('id') id: string, @InjectDB() db: any) { return await db.select().from(products).where(eq(products.id, parseInt(id)));}4. Validate input before hitting the database
Section titled “4. Validate input before hitting the database”Always use @Body(Schema) to validate input before any database operation. Veloce-TS will return a 422 automatically if validation fails.
Next Steps
Section titled “Next Steps”- Read the Pagination Guide for cursor and offset pagination helpers
- Learn about Dependency Injection
- Explore Caching to reduce database load
- Check the API Reference