NodeJSTypeORM

TypeORM

TypeORM is a TypeScript-native ORM that models your schema with decorated entity classes@Entity, @Column, @ManyToOne. If you've used Java's Hibernate or .NET's Entity Framework, the style will feel familiar. It supports both the Active Record and Data Mapper patterns and works across the SQL databases. This page covers entities, repositories, relations, and how TypeORM compares to Prisma and Sequelize.

Setup

Bash
npm install typeorm reflect-metadata pg

Requires decorator support in tsconfig.json

JS
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2021"
  }
}
Import `reflect-metadata` once, and enable decorator metadata
TypeORM relies on **decorator metadata** to read your entity types at runtime, so you must enable `experimentalDecorators` and `emitDecoratorMetadata` in `tsconfig.json`, and `import 'reflect-metadata'` **once** at your app's entry point (before any entity is loaded). Miss either and you'll get cryptic "metadata not found" / undefined-type errors. This setup friction is the price of the decorator style.
Defining an entity

TS
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany } from 'typeorm'
import { Post } from './Post'

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ unique: true })
  email: string

  @Column({ length: 100 })
  name: string

  @Column({ type: 'enum', enum: ['user', 'admin'], default: 'user' })
  role: string

  @OneToMany(() => Post, (post) => post.author)
  posts: Post[]

  @CreateDateColumn()
  createdAt: Date
}
The class IS the schema — decorators map fields to columns
Each property carries a decorator describing its column; the class doubles as the TypeScript type *and* the table definition. `@PrimaryGeneratedColumn`, `@Column`, `@CreateDateColumn`, and relation decorators (`@OneToMany`/`@ManyToOne`/`@ManyToMany`) generate the SQL. This "code-first" approach keeps the model in your language — appealing if you dislike [Prisma's separate schema file](/nodejs/prisma).
DataSource and repositories

TS
import { DataSource } from 'typeorm'
import { User } from './entities/User'

export const AppDataSource = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  entities: [User, Post],
  synchronize: false,            // see the warning below
  poolSize: 10,
})

await AppDataSource.initialize()   // connect ONCE at startup

// A repository is the query interface for an entity:
const userRepo = AppDataSource.getRepository(User)
CRUD via repository (Data Mapper)

TS
// Create:
const user = userRepo.create({ name: 'Ada', email: 'ada@x.com' })
await userRepo.save(user)

// Read:
const all  = await userRepo.find({ where: { role: 'user' }, take: 20 })
const one  = await userRepo.findOneBy({ id: Number(req.params.id) })
const withPosts = await userRepo.find({ relations: { posts: true } })

// Update & delete:
await userRepo.update({ id: 1 }, { name: 'Ada L.' })
await userRepo.delete({ id: 1 })
Repository (Data Mapper) vs Active Record — pick one style
TypeORM supports two patterns. **Data Mapper**: a `repository` object holds the query methods (above) — cleaner separation, better for testing. **Active Record**: entities extend `BaseEntity` and you call `User.find()`/`user.save()` directly. Both work; choose one and stay consistent. Most larger codebases prefer the repository style.
The query builder for complex SQL

TS
const result = await userRepo
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.role = :role', { role: 'admin' })       // parameterized
  .andWhere('post.published = :pub', { pub: true })
  .orderBy('user.createdAt', 'DESC')
  .take(20)
  .getMany()
Use named parameters in the query builder — not string interpolation
The query builder is great for joins and dynamic SQL, but always pass values as **named parameters** (`:role` with `{ role }`), never interpolate them into the string — same [SQL injection](/nodejs/databases-intro) rule as raw drivers. The builder parameterizes named placeholders for you; concatenating user input defeats that protection.
Relations and transactions

TS
// Eager-load with relations option or query builder joins:
const posts = await postRepo.find({ relations: { author: true } })

// Transaction:
await AppDataSource.transaction(async (manager) => {
  await manager.decrement(Account, { id: 1 }, 'balance', 100)
  await manager.increment(Account, { id: 2 }, 'balance', 100)
  // throw → rollback; resolve → commit
})
Migrations, never `synchronize` in production

Bash
npx typeorm migration:generate ./migrations/AddUserRole -d ./data-source.ts
npx typeorm migration:run -d ./data-source.ts
`synchronize: true` can drop columns and data — disable it in production
`synchronize: true` auto-aligns the database to your entities on every startup — convenient in development but **dangerous in production**: it can drop columns and lose data when it misreads a change. Always set `synchronize: false` for production and manage schema changes with generated, reviewed [migrations](/nodejs/database-migrations). This is the exact counterpart to [Sequelize's `sync`](/nodejs/sequelize) warning.
TypeORM vs Prisma vs Sequelize

TypeORM

Prisma

Sequelize

Schema style

Decorated classes

Single .prisma file

JS model definitions

Type safety

Good (entity types)

Excellent (generated)

Add-on, weaker

Patterns

Active Record + Data Mapper

Generated client only

Active Record

Setup friction

Decorators + reflect-metadata

Generate step

Minimal

Feels like

Hibernate / EF

Modern, declarative

Classic Node ORM

Choose TypeORM if you like decorator/entity-class modeling
TypeORM suits teams that prefer keeping the model as decorated classes in TypeScript and want the flexibility of two patterns plus a powerful query builder. If you want the strongest type-safety with the least ceremony, [Prisma](/nodejs/prisma) tends to win; if you want maximum maturity in plain JS, [Sequelize](/nodejs/sequelize). All three sit on the same [Postgres](/nodejs/postgresql)/[MySQL](/nodejs/mysql) drivers — the choice is about ergonomics, not capability.
Next
Add a blazing-fast cache and key–value store: [Redis & Caching](/nodejs/redis).