tech

TypeORM

TypeScript/JavaScript ORM - decorator-based entities, migrations, Active Record and Data Mapper

TL;DR

What: ORM for TypeScript and JavaScript (ES7, ES6, ES5).

Why: Decorator-based entities, migrations, multiple databases, Active Record & Data Mapper.

Quick Start

Install:

npm install typeorm reflect-metadata
npm install pg  # or mysql, sqlite3, etc.

Configure (data-source.ts):

import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'user',
  password: 'password',
  database: 'mydb',
  entities: ['src/entity/**/*.ts'],
  synchronize: true, // Disable in production
});

await AppDataSource.initialize();

Cheatsheet

CommandDescription
npx typeorm migration:createCreate migration
npx typeorm migration:runRun migrations
npx typeorm migration:revertRevert migration
npx typeorm schema:syncSync schema

Gotchas

Entity definition

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne } from 'typeorm';

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

  @Column()
  name: string;

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

  @Column({ nullable: true })
  age: number;

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

  @CreateDateColumn()
  createdAt: Date;

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

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

  @Column()
  title: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}

CRUD operations

import { AppDataSource } from './data-source';
import { User } from './entity/User';

const userRepository = AppDataSource.getRepository(User);

// Create
const user = userRepository.create({
  name: 'John',
  email: '[email protected]'
});
await userRepository.save(user);

// Read
const users = await userRepository.find();
const user = await userRepository.findOneBy({ id: 1 });

// Update
await userRepository.update(1, { name: 'Johnny' });

// Delete
await userRepository.delete(1);

Query builder

const users = await userRepository
  .createQueryBuilder('user')
  .where('user.age > :age', { age: 18 })
  .andWhere('user.isActive = :active', { active: true })
  .orderBy('user.name', 'ASC')
  .getMany();

// With relations
const usersWithPosts = await userRepository
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .getMany();

// Select specific fields
const names = await userRepository
  .createQueryBuilder('user')
  .select(['user.id', 'user.name'])
  .getMany();

Relations

// Find with relations
const user = await userRepository.findOne({
  where: { id: 1 },
  relations: ['posts'],
});

// Nested relations
const user = await userRepository.findOne({
  where: { id: 1 },
  relations: ['posts', 'posts.comments'],
});

Migrations

# Generate migration from entities
npx typeorm migration:generate -n CreateUsers

# Create empty migration
npx typeorm migration:create -n AddUserAge

# Run migrations
npx typeorm migration:run

# Revert last migration
npx typeorm migration:revert
// Migration file
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUsers1234567890 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE "user" (
        "id" SERIAL PRIMARY KEY,
        "name" VARCHAR NOT NULL
      )
    `);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE "user"`);
  }
}

Next Steps