Skip to content
Home Database TypeORM Basics

TypeORM Basics

Where developers are forged. · Structured learning · Free forever.
📍 Part of: ORM → Topic 6 of 7
TypeORM basics — entities, repositories, data source setup, relations (one-to-many, many-to-many), and query builder with TypeScript examples.
⚙️ Intermediate — basic Database knowledge assumed
In this tutorial, you'll learn
TypeORM basics — entities, repositories, data source setup, relations (one-to-many, many-to-many), and query builder with TypeScript examples.
  • Never use synchronize: true in production — use TypeORM migrations instead.
  • findOne returns null (not undefined) when not found — check explicitly.
  • Relations must be explicitly loaded with relations option or joins — TypeORM is lazy by default.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

TypeORM is a TypeScript ORM that maps classes to database tables. Define entities as TypeScript classes with decorators. Use DataSource.initialize() to connect. Access repositories with dataSource.getRepository(Entity) for CRUD, or QueryBuilder for complex queries. Supports PostgreSQL, MySQL, SQLite, and more.

Setting Up TypeORM

Example · TYPESCRIPT
1234567891011121314151617181920
// data-source.ts
import { DataSource } from 'typeorm';
import { User } from './entities/User';
import { Post } from './entities/Post';

export const AppDataSource = new DataSource({
    type: 'postgres',
    host: 'localhost',
    port: 5432,
    username: 'postgres',
    password: 'password',
    database: 'myapp',
    entities: [User, Post],
    synchronize: false,       // NEVER use true in production — use migrations
    logging: ['query', 'error'],
});

// Initialize once at app startup
await AppDataSource.initialize();
console.log('Database connected');
▶ Output
Database connected

Defining Entities

Example · TYPESCRIPT
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
// entities/User.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()
    name: string;

    @Column({ default: true })
    isActive: boolean;

    @CreateDateColumn()
    createdAt: Date;

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

// entities/Post.ts
import { Entity, PrimaryGeneratedColumn, Column,
         ManyToOne, JoinColumn } from 'typeorm';
import { User } from './User';

@Entity('posts')
export class Post {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column('text')
    content: string;

    @ManyToOne(() => User, (user) => user.posts)
    @JoinColumn({ name: 'author_id' })
    author: User;
}
▶ Output
// Entity classes map to database tables

CRUD with Repository

Example · TYPESCRIPT
1234567891011121314151617181920212223242526272829303132
const userRepo = AppDataSource.getRepository(User);

// CREATE
const user = userRepo.create({ name: 'Alice', email: 'alice@example.com' });
await userRepo.save(user);
console.log('Created user:', user.id);

// READ
const found = await userRepo.findOne({ where: { email: 'alice@example.com' } });
const all   = await userRepo.find({ where: { isActive: true } });

// READ with relations
const withPosts = await userRepo.findOne({
    where: { id: 1 },
    relations: { posts: true }
});
console.log(withPosts?.posts.length);

// UPDATE
await userRepo.update({ id: 1 }, { name: 'Alice Smith' });

// DELETE
await userRepo.delete({ id: 1 });

// QueryBuilder for complex queries
const activeUsers = await userRepo
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.posts', 'post')
    .where('user.isActive = :active', { active: true })
    .andWhere('post.id IS NOT NULL')
    .orderBy('user.createdAt', 'DESC')
    .getMany();
▶ Output
Created user: 1

🎯 Key Takeaways

  • Never use synchronize: true in production — use TypeORM migrations instead.
  • findOne returns null (not undefined) when not found — check explicitly.
  • Relations must be explicitly loaded with relations option or joins — TypeORM is lazy by default.
  • QueryBuilder is for complex queries; the Repository API covers 80% of CRUD needs.
  • TypeORM supports both Active Record (entity extends BaseEntity) and Data Mapper (repository) patterns.

Interview Questions on This Topic

  • QWhat is the difference between save() and update() in TypeORM?
  • QWhy is synchronize: true dangerous in production?
  • QHow do you load related entities in TypeORM?

Frequently Asked Questions

What is the difference between save() and update() in TypeORM?

save() handles both insert and update — if the entity has an id, it updates; if not, it inserts. It also triggers lifecycle hooks and cascades. update() is a direct SQL UPDATE — no lifecycle hooks, no cascade, but faster for bulk updates. Use save() for normal CRUD; use update() when performance matters or hooks are not needed.

Why should I never use synchronize: true in production?

synchronize: true automatically alters the database schema to match your entities on every application start. It can DROP columns that are removed from entities — losing data. Use TypeORM migrations instead: generate migration files, review them, and apply them deliberately.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousPrisma ORM BasicsNext →ActiveRecord vs DataMapper Pattern
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged