ORM Architecture: TypeORM Active Record / Data Mapper vs Prisma Client Engine
- Last updated
- Prerequisites:
- Relational Databases (Postgres/MySQL)
- TypeScript Decorators vs Code Generation
- nestjs
- typeorm
- prisma
- orm
- database
- sql
Read at your depth
The practical view
TypeORM uses TypeScript class decorators (`@Entity()`, `@PrimaryGeneratedColumn()`, `@ManyToOne()`) and repository injection (`@InjectRepository(User)`). Prisma uses a dedicated schema file (`schema.prisma`) and generates a custom, strongly-typed `PrismaClient` via `npx prisma generate`, injected into NestJS as a singleton service extending `OnModuleInit`.
The same idea in other frameworks
TypeORM `@InjectRepository(User)` vs Prisma `this.prisma.user.findMany()`.Spring Data JPA (Hibernate entity annotations) vs jOOQ (generated types).SQLAlchemy (declarative models) vs Tortoise ORM / Prisma Python.GORM (struct tags) vs sqlc / Ent (generated type-safe queries).Legacy vs modern
TypeORM Decorator Repository vs Prisma Type-Safe Client Service
TypeORM requires decorators and runtime type casting; Prisma generates strict types from the schema.
@InjectRepository(User) private userRepo: Repository<User>; // Decorator metadata reflectionthis.prisma.user.findUnique({ where: { id }, select: { id: true, email: true } }); // ✅ Exact return typeInterview gotchas
Context
Interviewers test your knowledge of ORM query planning and batching.
The mechanical answer
When fetching nested relations (e.g. `users.map(u => u.posts)`), naive TypeORM queries execute 1 query for users and N queries for each user's posts (unless explicitly using `JOIN FETCH` / `relations`). Prisma's Rust Query Engine uses an **automatic query dataloader batching mechanism**: it automatically coalesces nested relation queries into a single batched `WHERE id IN (...)` SQL query, eliminating N+1 queries without requiring manual joins.
Trap
In TypeORM, forgetting to specify `relations: ['posts']` will leave the relation `undefined` at runtime if lazy loading is disabled.
Context
Checks understanding of ES module evaluation and circular entity imports.
The mechanical answer
In TypeORM, bidirectional relationships (`@OneToMany(() => Post, post => post.user)`) require entity classes to import each other. If module loading evaluates `User` before `Post`, the reference to `Post` is `undefined` at decorator evaluation time, causing TypeORM metadata initialization to throw an error. This is why TypeORM relationship decorators strictly require the arrow function wrapper syntax `() => TargetEntity` to defer evaluation.
Trap
Writing `@OneToMany(Post, ...)` directly instead of `@OneToMany(() => Post, ...)` will crash on startup due to circular dependency TDZ.