0
0
Expressframework~5 mins

Repository pattern for data access in Express

Choose your learning style9 modes available
Introduction

The repository pattern helps organize how your app talks to data sources. It keeps data access code separate and easy to manage.

When you want to keep your data access code clean and separate from your app logic.
When you plan to switch databases or data sources without changing your app code.
When you want to write easier tests by mocking data access.
When your app grows and you want to avoid repeating data access code.
When you want a clear place to add caching or logging for data calls.
Syntax
Express
class Repository {
  constructor(model) {
    this.model = model;
  }

  async getAll() {
    return await this.model.find();
  }

  async getById(id) {
    return await this.model.findById(id);
  }

  async create(data) {
    return await this.model.create(data);
  }

  async update(id, data) {
    return await this.model.findByIdAndUpdate(id, data, { new: true });
  }

  async delete(id) {
    return await this.model.findByIdAndDelete(id);
  }
}

This example uses a class to wrap data access methods.

The model is usually a database model like a Mongoose model.

Examples
Extend the base repository to add custom methods for a specific data type.
Express
class UserRepository extends Repository {
  async findByEmail(email) {
    return await this.model.findOne({ email });
  }
}
Create an instance of the repository and call its methods to get data.
Express
const userRepository = new UserRepository(UserModel);
const users = await userRepository.getAll();
Sample Program

This Express app uses the repository pattern to manage user data. The UserRepository class handles all database calls. Routes call repository methods to get or create users.

This keeps the route code clean and focused on handling requests.

Express
import express from 'express';
import mongoose from 'mongoose';

// Define a simple User schema
const userSchema = new mongoose.Schema({
  name: String,
  email: String
});
const UserModel = mongoose.model('User', userSchema);

// Repository class
class Repository {
  constructor(model) {
    this.model = model;
  }

  async getAll() {
    return await this.model.find();
  }

  async getById(id) {
    return await this.model.findById(id);
  }

  async create(data) {
    return await this.model.create(data);
  }

  async update(id, data) {
    return await this.model.findByIdAndUpdate(id, data, { new: true });
  }

  async delete(id) {
    return await this.model.findByIdAndDelete(id);
  }
}

// UserRepository extends Repository
class UserRepository extends Repository {
  async findByEmail(email) {
    return await this.model.findOne({ email });
  }
}

const app = express();
app.use(express.json());

const userRepository = new UserRepository(UserModel);

// Connect to MongoDB (replace with your connection string)
mongoose.connect('mongodb://localhost:27017/testdb', { useNewUrlParser: true, useUnifiedTopology: true });

// Routes
app.get('/users', async (req, res) => {
  const users = await userRepository.getAll();
  res.json(users);
});

app.post('/users', async (req, res) => {
  const user = await userRepository.create(req.body);
  res.status(201).json(user);
});

app.get('/users/email/:email', async (req, res) => {
  const user = await userRepository.findByEmail(req.params.email);
  if (user) res.json(user);
  else res.status(404).send('User not found');
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
OutputSuccess
Important Notes

Always handle errors in real apps to avoid crashes.

The repository pattern helps if you change your database later; just update the repository code.

Use async/await to keep code easy to read and avoid callback hell.

Summary

The repository pattern separates data access from app logic.

It makes your code cleaner and easier to maintain.

You can add custom data methods by extending the base repository.