What is TypeORM?
TypeORM is an Object-Relational Mapping (ORM) library for TypeScript and JavaScript (Node.js) that runs on major database platforms such as PostgreSQL, MySQL, MariaDB, SQLite, and Microsoft SQL Server. It provides a way to map your application's classes (entities) to database tables, allowing you to interact with the database using object-oriented code instead of raw SQL. TypeORM supports both the Active Record and Data Mapper patterns, offers automatic migrations, and integrates seamlessly with modern frameworks like Express, NestJS, and Next.js.
Why TypeORM Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →In modern web development, managing database interactions efficiently is crucial. TypeORM matters because it:
- Type Safety: Leverages TypeScript decorators and type inference to catch errors at compile time.
- Productivity: Eliminates boilerplate SQL with intuitive APIs for CRUD, relations, and complex queries.
- Portability: Write code once and switch databases by changing a connection string.
- Migrations: Automatically generate and run database schema changes in a version-controlled way.
- Relations: Handle one-to-one, one-to-many, and many-to-many relationships with ease.
- Community & Ecosystem: Widely adopted with excellent documentation and integration guides.
Getting Started
Installation
Initialize a new Node.js project and install TypeORM along with your preferred database driver. For PostgreSQL:
npm init -y
npm install typeorm reflect-metadata pg
npm install -D typescript @types/node
Basic Configuration
Create a tsconfig.json and enable decorators and emitDecoratorMetadata:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
Define an ormconfig.json or use a programmatic configuration:
{
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "postgres",
"password": "password",
"database": "testdb",
"entities": ["src/entity/**/*.ts"],
"migrations": ["src/migration/**/*.ts"],
"synchronize": true,
"logging": false
}
Creating Your First Entity
An entity is a class mapped to a database table. Create src/entity/User.ts:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id!: number;
@Column()
firstName!: string;
@Column()
lastName!: string;
@Column({ unique: true })
email!: string;
}
Establishing a Connection
In your main entry file (e.g., src/index.ts), import reflect-metadata and create a connection:
import "reflect-metadata";
import { createConnection } from "typeorm";
import { User } from "./entity/User";
createConnection().then(async connection => {
console.log("Connected to database");
const userRepository = connection.getRepository(User);
const user = new User();
user.firstName = "John";
user.lastName = "Doe";
user.email = "john@example.com";
await userRepository.save(user);
console.log("User saved:", user);
}).catch(error => console.log(error));
Run with npx ts-node src/index.ts. TypeORM will create the table and insert the record.
Core Concepts
Entities & Decorators
Entities are decorated classes. Common decorators:
@Entity()– marks a class as an entity. Optional argument for table name.@PrimaryGeneratedColumn()– auto-incrementing primary key.@Column()– maps a property to a table column. Accepts options liketype,length,unique,nullable.@CreateDateColumn()/@UpdateDateColumn()– automatically set timestamps.
Repositories
Repositories provide an abstraction over entity operations. Use connection.getRepository(Entity) or Entity.getRepository() (Active Record). Example:
const userRepo = connection.getRepository(User);
// Find all
const users = await userRepo.find();
// Find by ID
const user = await userRepo.findOne(1);
// Update
await userRepo.update(1, { lastName: "Smith" });
// Delete
await userRepo.delete(1);
Relations
Define relationships between entities using decorators:
@OneToOne– e.g., User ↔ Profile@ManyToOne/@OneToMany– e.g., User ↔ Posts@ManyToMany– e.g., User ↔ Roles
Example: a User that has many Posts:
// src/entity/User.ts
import { OneToMany } from "typeorm";
import { Post } from "./Post";
@Entity()
export class User {
// ... other columns
@OneToMany(() => Post, post => post.user)
posts!: Post[];
}
// src/entity/Post.ts
import { ManyToOne, JoinColumn } from "typeorm";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id!: number;
@Column()
title!: string;
@ManyToOne(() => User, user => user.posts)
@JoinColumn({ name: "userId" })
user!: User;
}
Query Builder
For complex queries, use the QueryBuilder:
const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.where("user.email LIKE :email", { email: "%@example.com" })
.orderBy("user.id", "DESC")
.take(10)
.getMany();
Migrations
TypeORM can generate and run migrations to keep the schema in sync with your entities. First, configure CLI in package.json:
"scripts": {
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js"
}
Generate a migration after adding a new column:
npm run typeorm migration:generate -- -n AddAgeToUser
Run migrations:
npm run typeorm migration:run
Practical Examples
Building a REST API with Express
Install Express:
npm install express body-parser
npm install -D @types/express
Create src/app.ts:
import "reflect-metadata";
import { createConnection } from "typeorm";
import express from "express";
import { User } from "./entity/User";
const app = express();
app.use(express.json());
createConnection().then(connection => {
const userRepo = connection.getRepository(User);
// GET /users
app.get("/users", async (req, res) => {
const users = await userRepo.find();
res.json(users);
});
// POST /users
app.post("/users", async (req, res) => {
const { firstName, lastName, email } = req.body;
const user = userRepo.create({ firstName, lastName, email });
await userRepo.save(user);
res.status(201).json(user);
});
// GET /users/:id
app.get("/users/:id", async (req, res) => {
const user = await userRepo.findOne(req.params.id);
if (!user) return res.status(404).json({ error: "User not found" });
res.json(user);
});
// PUT /users/:id
app.put("/users/:id", async (req, res) => {
const user = await userRepo.findOne(req.params.id);
if (!user) return res.status(404).json({ error: "User not found" });
userRepo.merge(user, req.body);
await userRepo.save(user);
res.json(user);
});
// DELETE /users/:id
app.delete("/users/:id", async (req, res) => {
const result = await userRepo.delete(req.params.id);
if (result.affected === 0) return res.status(404).json({ error: "User not found" });
res.status(204).send();
});
app.listen(3000, () => console.log("Server running on port 3000"));
}).catch(error => console.log(error));
Using Relations in Queries
To load a user with their posts:
const userWithPosts = await userRepo.findOne(id, { relations: ["posts"] });
Or with QueryBuilder:
const user = await userRepo
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.where("user.id = :id", { id })
.getOne();
Best Practices
- Use
synchronize: falsein production – rely on migrations instead of auto-sync to avoid accidental data loss. - Naming conventions – Use snake_case for table and column names (e.g.,
@Column({ name: "first_name" })) or enablenamingStrategy. - Always specify column types explicitly – avoid TypeORM defaults that may differ between databases.
- Use indexes for performance – add
@Index()on columns used in WHERE, JOIN, or ORDER BY. - Prefer Data Mapper pattern – keep entities pure (no business logic) and use repositories/services for operations. This improves testability.
- Handle eager vs lazy loading – avoid N+1 queries by using
relationsor QueryBuilder joins. Set@ManyToOne(() => User, { eager: true })only when always needed. - Validate input before saving – combine class-validator with TypeORM decorators for consistent validation.
- Use environment variables for configuration – store database credentials in
.envand load viadotenv. - Run migrations in CI/CD pipelines – automate schema changes with
migration:runas a deploy step. - Keep entities focused – each entity should represent a single business domain object; avoid huge classes with hundreds of columns.
Conclusion
TypeORM provides a powerful and type-safe way to interact with databases in modern TypeScript applications. By leveraging its entity system, relations, query builder, and migration tools, developers can build scalable, maintainable web apps faster and with fewer errors. Whether you are building a small API or a large enterprise system, following the best practices outlined in this guide will help you avoid common pitfalls and keep your codebase clean. Start integrating TypeORM into your next project and experience the productivity boost of a full-featured ORM designed for the TypeScript ecosystem.