Database Recipes¶
spoonfeed provides 10 database-related recipes covering relational ORMs, document databases, and data lifecycle patterns. Choose a primary ORM/ODM recipe for your project, then layer on optional recipes like soft-delete, audit-trail, or transactional-outbox to handle cross-cutting data concerns.
Mutual exclusivity
The primary ORM/ODM recipes (typeorm-postgres, typeorm-mysql, prisma, mongoose, drizzle-postgres, kysely, mikro-orm) conflict with each other. Select one per project.
Recommended combinations
- Full data lifecycle:
typeorm-postgres+database-seeding+database-factories+soft-delete - Event-driven systems: any ORM recipe +
transactional-outbox+rabbitmqorbullmq - Compliance-heavy apps: any ORM recipe +
audit-trail+data-masking
TypeORM + PostgreSQL¶
TypeORM integration with the PostgreSQL driver. Uses the repository pattern with decorators.
| ID | typeorm-postgres |
| Category | Database |
| Dependencies | @nestjs/typeorm typeorm pg |
| Conflicts | typeorm-mysql, prisma, mongoose, drizzle-postgres, kysely, mikro-orm |
| Compatible with | All project types |
Environment variables:
| Variable | Default | Description |
|---|---|---|
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_USERNAME |
postgres |
PostgreSQL username |
DB_PASSWORD |
postgres |
PostgreSQL password |
DB_NAME |
app |
PostgreSQL database name |
Usage: Entities live in src/<module>/entities/. Inject repositories with @InjectRepository(). Use migrations for schema changes — never synchronize: true in production.
// src/users/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
@Column()
name: string;
}
// src/users/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async findById(id: string): Promise<User | null> {
return this.userRepository.findOneBy({ id });
}
}
Requires a running PostgreSQL instance
Ensure PostgreSQL is running locally or via Docker before starting the app. The docker-compose-dev recipe provides a pre-configured setup.
Pairs well with: database-seeding, database-factories, soft-delete, audit-trail
TypeORM + MySQL¶
TypeORM integration with the MySQL driver.
| ID | typeorm-mysql |
| Category | Database |
| Dependencies | @nestjs/typeorm typeorm mysql2 |
| Conflicts | typeorm-postgres, prisma, mongoose, drizzle-postgres, kysely, mikro-orm |
| Compatible with | All project types |
Environment variables:
| Variable | Default | Description |
|---|---|---|
DB_HOST |
localhost |
MySQL host |
DB_PORT |
3306 |
MySQL port |
DB_USERNAME |
root |
MySQL username |
DB_PASSWORD |
root |
MySQL password |
DB_NAME |
app |
MySQL database name |
Usage: Same repository pattern as TypeORM + PostgreSQL. Run migrations with pnpm typeorm migration:run.
Requires a running MySQL instance
Ensure MySQL is running locally or via Docker before starting the app.
Prisma¶
Schema-first ORM with a type-safe client generated from prisma/schema.prisma.
| ID | prisma |
| Category | Database |
| Dependencies | @prisma/client |
| Dev dependencies | prisma |
| Conflicts | typeorm-postgres, typeorm-mysql, mongoose, drizzle-postgres, kysely, mikro-orm |
| Compatible with | All project types |
Environment variables:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://postgres:postgres@localhost:5432/app |
Prisma database connection URL |
Usage: Edit prisma/schema.prisma, then run pnpm prisma generate and pnpm prisma migrate dev. Inject PrismaService for database access.
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/infrastructure/database/prisma.service';
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
async findById(id: string) {
return this.prisma.user.findUnique({ where: { id } });
}
}
Pairs well with: database-seeding, database-factories
Mongoose¶
MongoDB integration with the Mongoose ODM. Uses decorators for schema definitions.
| ID | mongoose |
| Category | Database |
| Dependencies | @nestjs/mongoose mongoose |
| Conflicts | typeorm-postgres, typeorm-mysql, prisma, drizzle-postgres, kysely, mikro-orm |
| Compatible with | All project types |
Environment variables:
| Variable | Default | Description |
|---|---|---|
MONGO_URI |
mongodb://localhost:27017/app |
MongoDB connection URI |
Usage: Define schemas with @Schema() and @Prop() decorators. Inject models with @InjectModel(). Use lean() for read-only queries.
Requires a running MongoDB instance
Ensure MongoDB is running locally or via Docker before starting the app.
Pairs well with: database-seeding, database-factories
Drizzle ORM + PostgreSQL¶
Lightweight type-safe ORM with best-in-class performance. SQL-first approach with TypeScript schema definitions.
| ID | drizzle-postgres |
| Category | Database |
| Dependencies | drizzle-orm pg |
| Dev dependencies | drizzle-kit |
| Conflicts | typeorm-postgres, typeorm-mysql, prisma, mongoose, kysely, mikro-orm |
| Compatible with | All project types |
Environment variables:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgres://postgres:postgres@localhost:5432/app |
PostgreSQL connection URL |
Usage: Schema definitions live in src/infrastructure/database/schema/. Run pnpm drizzle:generate after changes, then pnpm drizzle:migrate.
Kysely¶
Type-safe SQL query builder with zero overhead. You write the SQL; Kysely provides type safety.
| ID | kysely |
| Category | Database |
| Dependencies | kysely pg |
| Dev dependencies | kysely-ctl |
| Conflicts | typeorm-postgres, typeorm-mysql, prisma, mongoose, drizzle-postgres, mikro-orm |
| Compatible with | All project types |
Environment variables:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgres://postgres:postgres@localhost:5432/app |
PostgreSQL connection URL |
Usage: Define database types in src/infrastructure/database/types.ts. Use db.selectFrom(), db.insertInto(), etc. Migrations via kysely-ctl.
MikroORM + PostgreSQL¶
Data Mapper ORM with Unit of Work and Identity Map, well-suited for Domain-Driven Design.
| ID | mikro-orm |
| Category | Database |
| Dependencies | @mikro-orm/core @mikro-orm/nestjs @mikro-orm/postgresql @mikro-orm/migrations |
| Dev dependencies | @mikro-orm/cli |
| Conflicts | typeorm-postgres, typeorm-mysql, prisma, mongoose, drizzle-postgres, kysely |
| Compatible with | All project types |
Environment variables:
| Variable | Default | Description |
|---|---|---|
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_NAME |
app |
PostgreSQL database name |
DB_USERNAME |
postgres |
PostgreSQL username |
DB_PASSWORD |
postgres |
PostgreSQL password |
Usage: Entities in src/infrastructure/database/entities/. Changes persist on em.flush(). Migrations via pnpm mikro-orm migration:create.
Soft Delete¶
Mark records as deleted instead of removing them. Adds a deletedAt column to entities.
| ID | soft-delete |
| Category | Database |
| Dependencies | @nestjs/typeorm typeorm |
| Compatible with | All project types |
Usage: Extend SoftDeletableEntity for entities that should be soft-deleted. Use repository.softRemove() instead of remove(). Query soft-deleted records with @WithDeleted().
Audit Trail¶
Record entity changes with user, action, and diff. Captures who changed what and when.
| ID | audit-trail |
| Category | Database |
| Compatible with | All project types |
Usage: Apply AuditInterceptor globally or per-controller. Use @Auditable() decorator to mark handlers. Entries capture userId, action, entityName, entityId, changes, timestamp, and IP.
Transactional Outbox¶
Guarantee at-least-once event delivery alongside database writes using the outbox pattern.
| ID | transactional-outbox |
| Category | Database |
| Dependencies | @nestjs/typeorm typeorm |
| Compatible with | HTTP API, Microservice, Scheduled Worker, Full-Stack, Monorepo |
Usage: Call OutboxService.addMessage() within a transaction. Events are written to the outbox table atomically alongside business data, then published by a polling publisher.
Note
The outbox pattern guarantees at-least-once delivery. Consumers must be idempotent. Consider pairing with the idempotency recipe for downstream services.
Pairs well with: rabbitmq, bullmq, aws-sqs