Nest JS: Data Mapper vs Active Record Pattern (TypeORM)

Amir Mustafa
3 min readDec 23, 2023

--

→ When working in Nest JS Backend Framework, we have to save data in the database like every backend.

→ For interaction with the database we use many ORMs eg. JDBC for Java, and Sequelize for Express.js framework (Node.js).

→ Similar to the Nest.js framework (Node.js) we use TypeORM

Why use TypeORM:

→ No need to write raw queries.

→ Writing data models in one place. No repetition

→ Leverages OOP, therefore things like inheritance are easy to achieve

Code Traversal in Nest.js:

→ Code Travels from Module → Controller → Service → Repository (if using Data formatter pattern).

→ With Nest.js comes two approaches(i.e. patterns) when saving to a database that comes with TypeORM:

1. Active Record:

→ Using the Active Record approach, you define all your query methods inside the model (i.e. entity) itself and we save, remove, and load objects using model methods.

→ The Active Record pattern is an approach to access your database within your models.

File 1: User Entity File (This is the model skeleton)

Path: src/tasks/task.entity.ts

import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { TaskStatus } from "./task-status.enum";

@Entity()
export class Task extends BaseEntity { // Active Records always extends BaseEntity
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
title: string;

@Column()
description: string;

@Column()
status: TaskStatus;

}

→ Basically Controller file calls service files. In the service file, we write the DB logic

File 2: Service File:

// example how to save AR entity
const task = new Task()
user.task1 = "Cleaning"
user.task2 = "Workout"

await task.save()

// example how to remove AR entity
await task.remove()

// example how to load AR entities
const users = await Task.find({ skip: 2, take: 5 })
const newUsers = await Task.findBy({ isActive: true })
const timber = await Task.findOneBy({ firstName: "Timber", lastName: "Saw" })

2. Data Mapper:

→ Using the data mapper approach, we keep db interaction in a separate class called repository which is standard.

File 1: User Entity File (This is the model skeleton)

import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { TaskStatus } from "./task-status.enum";

@Entity()
export class Task {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
title: string;

@Column()
description: string;

@Column()
status: TaskStatus;

}

Repository:

Path: src/tasks/tasks.repository.ts

import { EntityRepository, Repository } from "typeorm";
import { Task } from "./task.entity";

@EntityRepository(Task)
export class TaskRepository extends Repository<Task> {

}

Service File:

Path: src/tasks/tasks.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { TaskStatus } from './task-status.enum';
import { GetTasksFilterDto } from './dto/get-tasks.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Task } from './task.entity';
import { CreateTaskDto } from './dto/create-task.dto';
import { TaskRepository } from './tasks.repository';

@Injectable()
export class TasksService {
constructor(
@InjectRepository(TaskRepository)
private taskRepository: TaskRepository<Task>,
) {}

async getTaskById(id: any): Promise<Task> {
const found = await this.taskRepository.findOne({
where: {
id,
},
});

if (!found) {
throw new NotFoundException(`Task with "${id}" does not exist!`);
}
return found;
}

async createTask(createTaskDto: CreateTaskDto): Promise<Task> {
const { title, description } = createTaskDto;

const task = this.taskRepository.create({
title,
description,
status: TaskStatus.OPEN,
});

const res = await this.taskRepository.save(task);
return res;
}
}

Module:

Path: src/tasks/tasks.module.ts

import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
import { TaskRepository } from './tasks.repository';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
imports: [TypeOrmModule.forFeature([TaskRepository])],
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}

→ The repository comes with lots of standard methods that make it easier to interact with the database.

→ First we chose databases like PostgreSQL, MySQL, etc.

→ Easily use their methods

Which one to use — Data Formatter vs Active Records?

→ Well this is mostly on requirements, for better code maintainability in production mostly Data Formatter pattern is used.

Conclusion:

Data Formatter and Active Records are two patterns in TypeOrm when Nest JS interacts with the database.

Mostly Data Formatter pattern is used i.e. having an additional repository layer in service.

Thank you for reading till the end 🙌 . If you enjoyed this article or learned something new, support me by clicking the share button below to reach more people and/or subscribe Happy Learnings !! to see some other tips, articles, and things I learn about and share there.

--

--

Amir Mustafa

JavaScript Specialist | Consultant | YouTuber 🎬. | AWS ☁️ | Docker 🐳 | Digital Nomad | Human. Connect with me on https://www.linkedin.com/in/amirmustafa1/