Active Record vs Data Mapper

Two fundamental ORM strategies — when the model IS the query versus a separate mapping layer.

Intermediate · 12 min read

Active Record vs Data Mapper

When working with databases through an ORM, two dominant patterns emerge: Active Record and Data Mapper. They differ in where persistence logic lives — inside the model or in a separate layer.

Active Record

In the Active Record pattern, the model object itself contains methods for saving, updating, and querying the database. The entity is the query builder.

import { BaseEntity, Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column()
  name!: string;

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

// Usage — the model IS the query
const user = new User();
user.name = 'Alice';
user.email = 'alice@example.com';
await user.save();                       // instance method

const found = await User.findOneBy({     // static method
  email: 'alice@example.com'
});
await found?.remove();

Data Mapper

In the Data Mapper pattern, entities are plain objects with no knowledge of the database. A separate repository or mapper handles all persistence operations.

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { AppDataSource } from './data-source';

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

  @Column()
  name!: string;

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

// Usage — a separate repository handles persistence
const userRepo = AppDataSource.getRepository(User);

const user = userRepo.create({ name: 'Alice', email: 'alice@example.com' });
await userRepo.save(user);

const found = await userRepo.findOneBy({ email: 'alice@example.com' });
if (found) await userRepo.remove(found);

Comparison

Active Record Data Mapper
Model contains persistence methods Models are plain objects (POJOs)
Quick to set up for simple CRUD Separation of concerns
Less boilerplate code More boilerplate but cleaner architecture
Tighter coupling to ORM framework Loosely coupled — easier to swap ORMs
Harder to unit test in isolation Easy to mock repositories in tests
Great for small-to-medium apps Best for large, domain-rich applications

When to Use Which?

Criterion Active Record Data Mapper
Project size Small / prototypes Medium to large
Team size Solo / small team Larger teams
Domain complexity Simple CRUD Complex business logic
Testability need Moderate High
ORM examples Sequelize, TypeORM (AR mode) TypeORM (DM mode), Prisma, MikroORM

TIP: Prisma takes the Data Mapper idea further — your models are defined in a schema file, and the generated client acts as the mapper. You never extend a base class.

CAUTION: Mixing both patterns in the same project creates confusion. Pick one and stay consistent across your codebase.


Part of the Backend Patterns series on Tekivex. Browse all tutorials or explore our open-source products.