TypeORM — synchronize: true Wiped a Production Table
TypeORM's synchronize: true silently dropped a 'created_at' column in production, wiping timestamps.
- TypeORM maps TypeScript classes to database tables via decorators
- DataSource.initialize() establishes the connection pool
- Repository API covers 80% of CRUD; QueryBuilder for complex joins
- Relations are lazy by default — must explicitly load with 'relations' option
- Never use synchronize: true in production — use migrations to avoid data loss
TypeORM lets you work with databases using TypeScript classes instead of SQL. You define a class like User, add some decorators, and TypeORM handles creating tables, inserting data, and querying — all with type safety and autocomplete.
TypeORM is the most popular ORM for TypeScript/Node.js applications. It follows the Active Record or Data Mapper pattern, letting you interact with your database using TypeScript classes and decorators rather than writing raw SQL.
The main advantage over raw queries: type safety. Refactoring column names propagates through the application with TypeScript compiler errors, not runtime bugs. The main disadvantage: complex queries are often easier to read as SQL than as QueryBuilder chains.
Setting Up TypeORM
The DataSource is the central hub of TypeORM — it holds the connection pool and entity registry. You configure it once at startup, usually in a separate file. The synchronize option should always be false in production; use migrations for schema evolution.
AppDataSource.destroy() on app shutdown, you'll leave open connections in the pool, causing a slow leak. Use process.on('SIGTERM', ...) in serverless or containers.Defining Entities
Entities are TypeScript classes decorated with @Entity. Each entity maps to a database table. Columns are defined with @Column, @PrimaryGeneratedColumn, etc. Relations use @OneToMany, @ManyToOne, @ManyToMany, and @JoinColumn to define foreign keys. Always specify the inverse side for bidirectional relations.
- The decorators tell TypeORM what SQL DDL to generate (create table, add foreign key).
- The class is used at runtime to hold query results.
- Changing a decorator does not alter the database unless you run a migration.
Column() with a default value does not automatically backfill existing rows.PrimaryGeneratedColumn()CRUD with Repository
The Repository pattern is the simplest way to interact with a single entity. Use dataSource.getRepository(Entity) to get a repository instance. For simple operations like create, read, update, delete, the repository methods are enough. For complex queries — joins, aggregations, subqueries — switch to QueryBuilder.
update() is a direct SQL UPDATE. Use save() for single entity operations that need hooks. Use update() for bulk updates where you don't need events.Relations: Loading Strategies and Pitfalls
TypeORM relations are lazy by default — they don't load related entities unless you explicitly ask for them. You can load relations via the 'relations' option in find/findOne, or via leftJoinAndSelect in QueryBuilder. Eager relations (set with { eager: true }) load automatically but can cause N+1 if used carelessly. The biggest production pitfall: assuming relations are always loaded.
find() on that entity will load all related records — even when you don't need them. This can easily become the source of slow queries. Always measure the actual query log.Migrations: Safe Schema Evolution
Migrations are the only safe way to change your database schema in production. TypeORM's migration system generates SQL files based on entity changes. Always review the generated SQL before running. Use migration:generate to create a migration, then migration:run to apply. Never edit migration files manually unless you understand the full impact.
- Each migration is a timestamped file that contains up and down methods.
- TypeORM compares current entity definitions against the database to generate the diff.
- Always verify the generated SQL — it may drop columns or tables unintendedly.
Synchronize: True Wiped a Production Table
- Never use synchronize: true in any environment connected to real data.
- Always review migration SQL before applying — even in staging.
- Use timestamp columns carefully — they are often removed first during refactors.
Key takeaways
Common mistakes to avoid
5 patternsUsing synchronize: true in production
Not loading relations explicitly
Creating DataSource per request (especially in serverless)
Assuming save() always performs INSERT
save() performs an UPDATE instead of INSERT, potentially overwriting existing data.insert() for new records if you want to guarantee INSERT. Or ensure id is undefined/omitted when creating new entities.Ignoring the N+1 query problem
Interview Questions on This Topic
What is the difference between save() and update() in TypeORM?
update() is a direct SQL UPDATE that only sets the provided columns — no lifecycle hooks, no cascade, and it skips validation. Use save() for normal CRUD where hooks matter; use update() for bulk updates where performance is critical.Frequently Asked Questions
That's ORM. Mark it forged?
3 min read · try the examples if you haven't