Let’s go through MVC, Service Layer, and Repository patterns in Node.js, which are essential for structuring scalable and maintainable applications.


1️⃣ MVC (Model-View-Controller) Pattern

  • Purpose: Separate concerns to make code modular and maintainable.

  • Common in web applications, especially with Express.

Components

LayerResponsibility
ModelDefines data structure and interacts with the database (e.g., Mongoose)
ViewPresentation layer (optional in APIs; in Node APIs, often JSON responses)
ControllerHandles incoming requests, calls services, returns responses

Example (Express + Mongoose)

// models/User.js
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({ name: String, email: String });
module.exports = mongoose.model("User", UserSchema);
 
// controllers/userController.js
const User = require("../models/User");
 
exports.getAllUsers = async (req, res) => {
  const users = await User.find();
  res.json(users);
};
 
// routes/userRoutes.js
const express = require("express");
const router = express.Router();
const { getAllUsers } = require("../controllers/userController");
 
router.get("/users", getAllUsers);
 
module.exports = router;

2️⃣ Service Layer

  • Purpose: Encapsulate business logic separate from controllers and models.

  • Controllers become thin → mainly handle HTTP requests and responses.

  • Makes code testable and reusable.

Example

// services/userService.js
const User = require("../models/User");
 
exports.getAllUsers = async () => {
  // business logic, e.g., filter, sort, caching
  return await User.find();
};
 
// controllers/userController.js
const userService = require("../services/userService");
 
exports.getAllUsers = async (req, res) => {
  const users = await userService.getAllUsers();
  res.json(users);
};
  • Benefits:

    • Easier unit testing (services can be tested without HTTP)

    • Reusable business logic


3️⃣ Repository Pattern

  • Purpose: Abstract database access behind a repository layer

  • Makes it easy to switch databases or mock data access in tests

  • Focuses solely on data access, not business logic

Example

// repositories/userRepository.js
const User = require("../models/User");
 
exports.findAll = async () => await User.find();
exports.findById = async (id) => await User.findById(id);
exports.create = async (data) => await User.create(data);
 
// services/userService.js
const userRepo = require("../repositories/userRepository");
 
exports.getAllUsers = async () => {
  return await userRepo.findAll();
};
  • Benefits:

    • Decouples data access from services

    • Easier to swap DB (MongoDB → PostgreSQL)

    • Improves testability via mocks


4️⃣ Layered Architecture Summary

Controller  -> handles HTTP, calls Service
Service     -> contains business logic, calls Repository
Repository  -> data access layer (DB queries)
Model       -> database schema/ORM
  • Flow Example:
    Client → Controller → Service → Repository → Database → Response

5️⃣ Key Takeaways

  1. MVC → separates presentation (View), HTTP handling (Controller), and data (Model)

  2. Service Layer → centralizes business logic, keeps controllers thin

  3. Repository Pattern → abstracts database access for decoupling and testability

  4. Benefits of this structure:

    • Modular, maintainable, testable

    • Easier to scale for large applications