Implementing Repository Pattern in Node.js and TypeScript

The repository pattern is a valuable design pattern that separates the data access logic from the rest of the application.

1. Defining the Repository Interface

Create an interface that outlines the basic CRUD (Create, Read, Update, Delete) operations for your data entity. For example, a UserRepository interface might look like this:

// IUserRepository.ts

export interface IUserRepository {
  findById(id: string): Promise<User | null>;
  findAll(): Promise<User[]>;
  create(user: User): Promise<User>;
  update(id: string, user: User): Promise<User | null>;
  delete(id: string): Promise<boolean>;
}

2. Implementing the Repository

Now, implement the repository for the User entity:

// UserRepository.ts

import { IUserRepository } from './IUserRepository';
import { User } from '../models/User';
import { Database } from '../services/Database'; // Assume a database service exists

export class UserRepository implements IUserRepository {
  private database: Database;

  constructor(database: Database) {
    this.database = database;
  }

  async findById(id: string): Promise<User | null> {
    // Implementation to fetch user by ID from the database
  }

  async findAll(): Promise<User[]> {
    // Implementation to fetch all users from the database
  }

  async create(user: User): Promise<User> {
    // Implementation to create a new user in the database
  }

  async update(id: string, user: User): Promise<User | null> {
    // Implementation to update a user in the database
  }

  async delete(id: string): Promise<boolean> {
    // Implementation to delete a user from the database
  }
}

3. Using the Repository in Your Application

In your application, you can now use the UserRepository without worrying about the underlying data storage details:

// App.ts

import { UserRepository } from './repositories/UserRepository';
import { User } from './models/User';
import { Database } from './services/Database';

const database = new Database(); // Initialize your database connection
const userRepository = new UserRepository(database);

async function exampleUsage() {
  const user = await userRepository.create({
    id: '1',
    name: 'John Doe',
    email: 'john@example.com',
  });

  console.log('Created user:', user);

  const foundUser = await userRepository.findById('1');
  console.log('Found user:', foundUser);

  const allUsers = await userRepository.findAll();
  console.log('All users:', allUsers);
}

exampleUsage();

Summary

By adopting the repository pattern, your application benefits from a clean separation of concerns, making it easier to maintain and extend. The repository provides a clear interface for data operations, allowing you to focus on the application's business logic.

Objective: The article aims to guide developers in effectively implementing the Repository Pattern for streamlined data access in Node.js and TypeScript.

Interface Definition: Introduced the IUserRepository interface, providing a blueprint for CRUD operations tailored to the User entity.

Repository Implementation: Demonstrated the creation of the UserRepository class, implementing the specified interface to encapsulate data access logic.

Separation of Concerns: Emphasized the significance of cleanly separating data access logic from the main application, promoting modular and maintainable code.

Usage Example: Illustrated the practical usage of the repository in a sample application, showcasing CRUD operations with the User entity.

Code Organization: Explored the benefits of adopting the repository pattern, enhancing code organization and facilitating easier maintenance.