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
npm install typeorm reflect-metadata pg
Requires decorator support in tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2021"
}
}Defining an entity
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
}DataSource and repositories
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)
// 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 })The query builder for complex SQL
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()Relations and transactions
// 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
npx typeorm migration:generate ./migrations/AddUserRole -d ./data-source.ts npx typeorm migration:run -d ./data-source.ts
TypeORM vs Prisma vs Sequelize
TypeORM | Prisma | Sequelize | |
|---|---|---|---|
Schema style | Decorated classes | Single | 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 |