<link rel="stylesheet" href="styles-3GBANQL6.css">
← Back to blog

TypeORM

Exploring Database Management with TypeORM

Exploring Database Management with TypeORM

Understanding Database-Specific Column Types

Utilizing database-specific column types ensures that data is stored and retrieved efficiently. Each type optimally represents the kind of data being stored, which can enhance performance and data integrity.

Benefits: Improves storage efficiency, enforces data integrity, and allows for optimized queries.


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

    @Column({ type: 'varchar', length: 100 })
    name: string;

    @Column({ type: 'int' })
    age: number;

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

    @Column({ type: 'date' })
    birthDate: Date;

    @Column({ type: 'decimal', precision: 10, scale: 2 })
    balance: number;

    @Column({ type: 'json' })
    preferences: { theme: string; notifications: boolean };

    @Column({ type: 'text' })
    bio: string;
}
            

Entity Manager

The Entity Manager is crucial for managing database operations, providing a way to manipulate and persist data. It serves as the primary interface to interact with the database.

Benefits: Simplifies data manipulation and provides a consistent API for performing CRUD operations.


const user = await entityManager.findOne(User, { where: { id: 1 } });
user.name = 'Updated Name';
await entityManager.save(user);
            

Repositories and Custom Repositories

Repositories abstract data access logic, making it easier to manage and interact with entities. Custom repositories allow for tailored data access methods that fit specific application needs.

Benefits: Enhances code organization, promotes reusability, and encapsulates data access logic.


@EntityRepository(User)
export class UserRepository extends Repository {
    async findActiveUsers() {
        return this.find({ where: { isActive: true } });
    }
}
            

Clean Object-Relational Model

Separating application logic from the database schema creates a clean and maintainable structure. This promotes the single responsibility principle, making it easier to manage changes.

Benefits: Facilitates easier testing, promotes maintainability, and enhances clarity in code structure.


class UserService {
    constructor(private userRepository: UserRepository) {}

    async getActiveUsers() {
        return await this.userRepository.findActiveUsers();
    }
}
            

Declarative Example: Defining a User Entity

Declarative syntax for defining entities enhances clarity and reduces boilerplate code. It provides a clear representation of the data model.

Benefits: Improves readability and reduces potential errors during model definition.


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

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

    @Column({ type: 'varchar', length: 100 })
    name: string;

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

    @Column({ type: 'date' })
    birthDate: Date;

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

Associations (Relations)

Defining associations between entities establishes relationships, allowing for complex data structures. This is essential for representing real-world relationships in the database.

Benefits: Enables structured data retrieval, enforces referential integrity, and simplifies complex queries.


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

    @Column()
    title: string;

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

Eager and Lazy Relations

Eager loading fetches related entities immediately, while lazy loading fetches them on demand. This allows for flexibility based on application needs.

Benefits: Optimizes performance and resource usage depending on data access patterns.


const users = await getRepository(User).find({ relations: ['posts'] }); // Eager loading
            

const user = await getRepository(User).findOne(1);
const posts = await user.posts; // Lazy loading
            

Uni-directional, Bi-directional, and Self-Referenced Relations

These relationships define how entities connect. Bi-directional relationships allow navigation in both directions, while self-referenced relationships can represent hierarchies.

Benefits: Provides flexibility in data modeling and allows for complex relationships to be represented accurately.


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

    @Column()
    name: string;

    @ManyToOne(() => Category, (category) => category.subcategories, { nullable: true })
    parent: Category;

    @OneToMany(() => Category, (category) => category.parent)
    subcategories: Category[];
}
            

Supports Multiple Inheritance Patterns

Inheritance allows for defining common properties in a base class while enabling specialized behavior in derived classes. This promotes code reuse.

Benefits: Enhances organization and minimizes duplication of code across similar entities.


abstract class BaseEntity {
    @PrimaryGeneratedColumn()
    id: number;
}

@Entity()
export class User extends BaseEntity {
    @Column()
    name: string;
}

@Entity()
export class Admin extends BaseEntity {
    @Column()
    adminLevel: number;
}
            

Cascades

Cascade operations allow related entities to be affected when an entity is modified. This simplifies data management in related records.

Benefits: Ensures data consistency and reduces the need for manual updates across related entities.


@OneToMany(() => Post, (post) => post.user, { cascade: true })
posts: Post[];
            

Indices

Creating indices on frequently queried columns improves database performance by speeding up data retrieval operations.

Benefits: Enhances query performance, particularly for large datasets.


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

Transactions

Transactions ensure that a series of operations are executed as a single unit. If one operation fails, all changes are rolled back, maintaining data integrity.

Benefits: Ensures consistency and reliability in data operations, especially in complex workflows.


await getConnection().transaction(async (entityManager) => {
    const user = new User();
    user.name = 'New User';
    await entityManager.save(user);

    const post = new Post();
    post.title = 'User Post';
    post.user = user;
    await entityManager.save(post);
});
            

Migrations and Automatic Migrations Generation

Migrations help manage changes in the database schema over time. Automatic generation reduces manual effort and minimizes errors.

Benefits: Facilitates smooth schema updates and helps keep track of changes in version control.


npx typeorm migration:generate -n MigrationName
            

Connection Pooling

Connection pooling optimizes the performance of database applications by reusing existing connections rather than creating new ones for each request.

Benefits: Reduces latency and overhead associated with establishing connections, improving overall application responsiveness.


createConnection({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'test',
    password: 'test',
    database: 'test',
    extra: {
        connectionLimit: 10, // Pool size
    },
});
            

Replication

Replication enhances data availability and reliability by copying data across multiple database instances, which can be essential for load balancing and fault tolerance.

Benefits: Increases data availability, improves performance through load balancing, and provides data redundancy.


{
    type: 'mysql',
    replication: {
        master: {
            host: 'master-db-host',
            username: 'root',
            password: 'password',
            database: 'test',
        },
        slaves: [
            {
                host: 'slave-db-host',
                username: 'root',
                password: 'password',
                database: 'test',
            },
        ],
    },
}
            

Using Multiple Database Instances

Defining multiple connections allows an application to interact with different databases or instances, which can be useful in microservices or multi-tenant architectures.

Benefits: Provides flexibility to manage diverse data sources and optimizes resource utilization.


const connection1 = await createConnection({ /* connection options for DB1 * / });
const connection2 = await createConnection({ /* connection options for DB2 * / });
            

Working with Multiple Database Types

Supporting multiple database types allows applications to use the best tool for specific tasks. It also promotes integration with legacy systems.

Benefits: Enhances application flexibility and allows for leveraging the strengths of different databases.


const mysqlConnection = await createConnection({
    type: 'mysql',
    /* MySQL options */
});

const mongoClient = await MongoClient.connect('mongodb://localhost:27017');
const db = mongoClient.db('test');
            

Cross-Database and Cross-Schema Queries

Cross-database queries allow accessing data across different databases, while cross-schema queries enable working with different schemas within the same database.

Benefits: Promotes data integration and facilitates complex data operations across multiple data sources.


SELECT * FROM schema1.users u
JOIN schema2.orders o ON u.id = o.userId;
            

Elegant Syntax, Flexible and Powerful QueryBuilder

Using a powerful QueryBuilder allows for dynamic and complex query creation, enabling developers to build efficient queries programmatically.

Benefits: Enhances code readability and flexibility, allowing for easier maintenance and adaptation to changes.


const users = await getRepository(User)
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.posts', 'post')
    .where('user.isActive = :isActive', { isActive: true })
    .getMany();
            

Left and Inner Joins

Using joins allows for retrieving related data from multiple tables efficiently. Inner joins return matching records, while left joins return all records from one table regardless of matches.

Benefits: Enhances query capabilities, allowing for complex data retrieval scenarios.


const posts = await getRepository(Post)
    .createQueryBuilder('post')
    .innerJoin('post.user', 'user')
    .where('user.isActive = :isActive', { isActive: true })
    .getMany();
            

Proper Pagination for Queries Using Joins

Implementing pagination in queries allows for efficient data retrieval, especially with large datasets, by limiting the number of results returned.

Benefits: Improves application performance and user experience by loading data in manageable chunks.


const [posts, total] = await getRepository(Post)
    .createQueryBuilder('post')
    .innerJoinAndSelect('post.user', 'user')
    .skip(10) // Skip the first 10 results
    .take(10) // Take the next 10 results
    .getManyAndCount();
            

Query Caching

Query caching stores the results of frequently executed queries, improving performance by reducing the need to hit the database each time.

Benefits: Significantly enhances performance for read-heavy applications by minimizing database load.


const users = await getRepository(User)
    .createQueryBuilder('user')
    .cache(60000) // Cache for 60 seconds
    .getMany();
            

Streaming Raw Results

Streaming allows for processing large datasets in a memory-efficient manner, as it retrieves results incrementally rather than all at once.

Benefits: Reduces memory consumption and improves performance when handling large volumes of data.


const query = getConnection()
    .createQueryBuilder()
    .select('user')
    .from(User, 'user');

const stream = await query.stream();
stream.on('data', (row) => {
    console.log(row);
});
            

Logging

Setting up logging for database operations helps in monitoring and debugging issues. It provides insights into query performance and error handling.

Benefits: Facilitates troubleshooting and performance tuning by providing valuable execution data.


createConnection({
    type: 'mysql',
    logging: ['query', 'error'], // Log queries and errors
});
            

Listeners and Subscribers (Hooks)

Event listeners and subscribers allow developers to react to certain lifecycle events of entities, providing a way to execute custom logic automatically.

Benefits: Enhances maintainability and allows for automated actions without modifying the core logic.


import { EventSubscriber, EntitySubscriberInterface, InsertEvent } from 'typeorm';

@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface {
    listenTo() {
        return User;
    }

    afterInsert(event: InsertEvent) {
        console.log(`User ${event.entity.name} has been inserted.`);
    }
}
            

Supports Closure Table Pattern

The closure table pattern is useful for managing hierarchical data structures, allowing for efficient querying of parent-child relationships.

Benefits: Simplifies querying hierarchical data and improves performance for tree-like structures.


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

    @Column()
    ancestorId: number;

    @Column()
    descendantId: number;

    @Column()
    depth: number;
}
            

Schema Declaration in Models or Separate Configuration Files

Defining schema directly in models or in configuration files enhances clarity and maintainability, allowing for clear data structure definitions.

Benefits: Improves organization of the codebase and simplifies schema updates.


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

    @Column({ type: 'varchar', length: 200 })
    name: string;

    @Column({ type: 'decimal' })
    price: number;

    @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
    createdAt: Date;

    @Column({ type: 'enum', enum: ['available', 'out_of_stock'] })
    status: 'available' | 'out_of_stock';
}
            

LET'S BUILD WHAT'S NEXT

Ready to turn your idea into a real solution?

Let's discuss how we can help you create value with technology.

Let's Talk