Talently
Talently
TypeORM

TypeORM

The TypeScript ORM for Node.js with multi-database support

TypeORM is an ORM for Node.js and TypeScript that supports relational and non-relational databases, including PostgreSQL, MySQL, SQLite, MongoDB, and more. Using TypeScript decorators to define entities and relationships, it provides two usage patterns, Active Record and Data Mapper, with full support for migrations, complex relationships, and is the official recommended persistence library in the NestJS ecosystem.

TypeScriptNode.jsDecoratorsSQL

Market demand

TypeORM has high demand especially in NestJS projects where it is the most used persistence library. Its adoption has grown significantly with the growth of NestJS and TypeScript in backend development.

Official recommended ORM in NestJSHigh demand in TypeScript projectsAccelerated growth with NestJS ecosystem

Technical requirements

Intermediate

Requires solid mastery of TypeScript including decorators, generics, and advanced types. Familiarity with relational databases, SQL, and ORM concepts like entities, relationships, and migrations is essential for TypeORM projects in production.

Use cases

Real Projects

TypeORM is used to develop:

  • Persistence layer in NestJS applications
  • TypeScript APIs with relational data models
  • Systems with multiple databases in the same application
  • Projects migrating from Sequelize to TypeScript with strict typing

Types of Company

TypeORM is adopted by:

  • Startups with NestJS and TypeScript stack
  • Companies migrating Node.js backends to TypeScript
  • Teams developing APIs with NestJS and PostgreSQL
  • Companies with full-stack TypeScript projects

Production Scenarios

TypeORM is widely used in production environments such as:

  • REST and GraphQL APIs with NestJS and TypeORM
  • Systems with complex and typed domain models
  • Applications with multiple relational data sources
  • Backends with typed repositories for each entity

Scalability

TypeORM offers multiple mechanisms to scale applications:

  • Connection pooling for efficient connection management
  • QueryBuilder for complex typed queries
  • Built-in query caching
  • Automatic and manual migrations for schema management

Advantages and Disadvantages

Advantages

First-class TypeScript with decorators for defining entities and relationships.

Support for both Active Record and Data Mapper patterns in the same project.

Official and deep integration with NestJS through @nestjs/typeorm.

Disadvantages

Inconsistent maintenance history with periods of reduced activity.

Known bugs in some advanced functionalities that have been slow to resolve.

Prisma has gained traction as a more modern alternative with better development experience.

Comparison

Advantages of Prisma

  • Declarative schema as single source of truth
  • Automatically generated types without decorators
  • Better development experience and fewer runtime errors

Considerations

Prisma has a better TypeScript development experience with automatically inferred types. TypeORM is preferable when the Active Record pattern is needed, when inheriting existing TypeORM code, or when integration with NestJS through specific modules is a priority.

Basic questions

TypeORM uses TypeScript decorators to define entities and relationships directly in classes, generating more expressive and typed code. The integration with NestJS through @nestjs/typeorm is official and deep, with typed repository injection directly into services.
Active Record extends BaseEntity in entities and has persistence methods like save, find, and remove directly on the entity class. Data Mapper separates persistence logic into repositories independent of the entity. Data Mapper is preferable for large projects where separating the domain from persistence infrastructure is desired.
Using the @Entity decorator on the class, @Column for each property that maps to a column, @PrimaryGeneratedColumn for the auto-generated primary key, and @OneToMany, @ManyToOne, @ManyToMany, or @OneToOne for relationships with their corresponding decorators on both sides of the relationship.
Entities are TypeScript classes with defined types that the IDE can verify, relationships are typed, and the compiler catches errors for accessing non-existent properties. Repositories are generic Repository that provide typed methods, eliminating a class of runtime errors that JavaScript ORMs only detect during execution.
TypeORM can automatically generate migrations by comparing entities with the current database schema, or they can be created manually for more control. In production, synchronize must be disabled and migrations are applied explicitly, ensuring schema changes are reviewed and versioned before being applied.
For complex queries with multiple conditional JOINs, subqueries, aggregations, or dynamic conditions that the repository's find methods cannot express cleanly. QueryBuilder allows building complex SQL programmatically while maintaining TypeScript typing.
It occurs when a list of entities is loaded and then lazy relationships of each one are accessed, generating one query per entity. It is solved using eager loading with relations in find options, or with leftJoinAndSelect in QueryBuilder to load relationships in the same query with JOIN.
In NestJS projects with TypeScript where official integration and typed repositories are priorities, when the team prefers the decorator pattern similar to Java, or when an ORM with support for both relational databases and MongoDB in the same library is needed.

Technical questions

By creating an explicit intermediate entity that represents the join table with its own additional columns, and using ManyToOne relationships from that entity to the two original entities. This is necessary when the many-to-many relationship has its own attributes that don't fit in a simple join table.
By implementing the EntitySubscriberInterface and decorating the class with @EventSubscriber. Methods like beforeInsert, afterUpdate, or beforeRemove are implemented to execute cross-cutting logic like auditing, normalization, or notifications when entities change state, without coupling this logic to services.
Eager loading with eager: true on the relationship decorator loads the relationship automatically on every query, which can generate unnecessary JOINs. Lazy loading with lazy: true returns a Promise that resolves when the relationship is accessed, requiring await. In practice, explicitly loading relationships per query with relations is preferred for greater control.
By adding @DeleteDateColumn to the entity which TypeORM manages automatically. When calling softDelete or softRemove, it sets the column instead of deleting the record. Normal queries automatically exclude soft-deleted records and they can be included with withDeleted in find options or in QueryBuilder.
Using the DataSource or EntityManager with transaction() that receives an async function with the transaction's entityManager. In NestJS, DataSource can be injected into the service and dataSource.transaction() used to group multiple operations. The @Transaction decorator with @TransactionManager can also be used although this approach is deprecated in recent versions.
Using inheritance strategies with @TableInheritance on the parent entity. STI with discriminatorColumn stores all subclasses in one table. CTI with @ChildEntity on each subclass creates tables per subclass with JOINs. The choice depends on normalization requirements and the project's data access patterns.
By configuring the DataSource with entities and migration path, using typeorm migration:generate to automatically generate the migration by comparing entities with the current schema, reviewing the generated SQL to verify correctness, and executing typeorm migration:run in the deployment process before starting the application.
Using findAndCount which returns records and the total in a single call for offset pagination, with take for the limit and skip for the offset. For large tables, cursor pagination is implemented using where with the id of the last record, consistent order, and take, avoiding the degraded performance of high skip values on tables with millions of records.

Advanced questions

Using the Data Mapper pattern with custom repositories that extend Repository to encapsulate domain-specific queries, injected into services via @InjectRepository. NestJS modules encapsulate each domain with its entities, repositories, and services, importing TypeOrmModule.forFeature with the module's entities.
When TypeORM's known bugs impact critical project functionalities, when the team prefers Prisma's declarative model with generated types without decorators, or when MikroORM's more robust Unit of Work provides better consistency. The migration is costly and must be justified with real problems in the project.
By creating write repositories with complete entities for domain operations and read repositories with optimized SQL projections using QueryBuilder for query operations. Command handlers use write repositories and query handlers use read ones, allowing independent optimizations on each side.
Using database transactions for synchronous operations where all changes must be atomic, implementing the Outbox pattern for domain events that must fire after commit with delivery guarantees, and using optimistic locking with @VersionColumn to detect conflicts in concurrent operations.
By enabling query logging to identify slow queries or N+1, using explain analyze in PostgreSQL to analyze execution plans, adding indexes on frequently filtered columns, using projections with select to fetch only necessary columns, implementing TypeORM's query cache for frequent queries, and reviewing the connection pool.
Using an in-memory SQLite test database or PostgreSQL in Docker for integration tests that verify real ORM behavior with the database, mocking repositories with jest.mock for unit tests of services that shouldn't depend on the database, and applying migrations before tests with synchronize: true in the test environment.

Common interview mistakes

Leaving synchronize: true in production allows TypeORM to automatically modify the schema on startup, which can generate data loss or unreviewed changes. It is one of the most serious and frequent configuration errors in TypeORM projects taken to production without experience.
Not correctly defining mappedBy on the inverse side of relationships generates unnecessary intermediate tables or unexpected behaviors in queries. It is a frequent error among developers new to TypeORM who don't understand how foreign keys are mapped.
Proposing TypeORM for a new project without evaluating Prisma or MikroORM reflects not following the current TypeScript ORM ecosystem. Knowledge that Prisma has gained popularity and being able to justify choosing TypeORM over these alternatives is expected.
Attempting to express complex queries with the repository's find methods when QueryBuilder would be more appropriate generates unreadable code or inefficient queries. Not knowing QueryBuilder reflects limited experience with TypeORM on projects with complex data requirements.
Not correctly configuring the connection pool or not closing the DataSource in tests generates connection leaks. In production, this can exhaust the connection pool under load, generating timeouts that are difficult to diagnose.
Applying TypeORM-generated migrations without reviewing the SQL can generate unintended destructive changes like column drops. Knowledge that automatic migrations should always be reviewed before being applied in production is expected.