A Deep Dive into the Repository Pattern for Node.js: Structure, Benefits, and Best Practices

A Deep Dive into the Repository Pattern for Node.js: Structure, Benefits, and Best Practices

In this article, we explore the Repository Pattern in Node.js, a powerful design pattern that abstracts the data layer of your application. We will delve into its role in maintaining clean, scalable, and testable code, and demonstrate how it helps manage database interactions. Through practical examples, we’ll show how to implement the pattern efficiently, with a focus on error handling across multiple layers of the application (repository, service, and controller). By following best practices for structure and error management, you’ll learn how to build applications that are easier to maintain, extend, and troubleshoot.

1. Repository Layer (Data Access Layer):

  • Responsibility: This layer is responsible for interacting with the database or external APIs. It should handle any low-level errors related to data fetching or persistence, such as database connection issues, query errors, or data not found.
  • Error Handling: Errors that occur due to database constraints (e.g., violating a foreign key constraint) or failure to retrieve data (e.g., not finding the requested resource) should be handled here. This allows the service or controller layer to focus on business logic without worrying about low-level data access details.

Example:

  • If a user does not exist in the database or if the query fails, the repository can throw a CustomError or any error that makes sense for the situation.

const userRepo = (userId) => {
    const user = users.find(user => user.id === userId);
    if (!user) {
        throw new CustomError(404, "User not found");
    }
    return user;
}
        

2. Service Layer (Business Logic Layer):

  • Responsibility: This layer is responsible for handling business logic. It interacts with repositories (or other services) to perform operations based on the use case. It should handle errors that result from business logic violations or invalid operations, such as trying to perform an operation on invalid data or an action that doesn't make sense in the current context.
  • Error Handling: If an error occurs during a business process (e.g., the current user is not authorized to access certain data), it should throw an error or return an appropriate response. The service layer is typically where the core business logic is validated, such as checking if the user has the necessary permissions.

Example:

  • If a user tries to access a resource they don't own, the service can throw a CustomError to indicate that the user is unauthorized.

const userService = (userId) => {
    const user = userRepo(userId); // Call the repo
    if (user.status !== "active") {
        throw new CustomError(403, "User is not active");
    }
    return user;
}
        

3. Controller Layer (API Endpoint Layer):

  • Responsibility: The controller layer is responsible for handling HTTP requests and responses. It should not contain business logic or data access logic but should instead delegate to the service layer to perform the necessary operations. It can handle errors in terms of returning the appropriate HTTP response with a status code and message.
  • Error Handling: In the controller, you catch any errors thrown by the service layer or repository and map them to a suitable HTTP response. It’s where the application can format the error in a user-friendly way and ensure that proper HTTP status codes are returned.

Example:

  • If the service or repo throws an error, the controller catches it and returns an appropriate response to the client.

const userController = (req, res) => {
    const userId = req.params.userId;
    try {
        const user = userService(userId); // Call the service
        res.status(200).json(user);
    } catch (error) {
        console.error(`Error: ${error.message}`);
        res.status(error.statusCode || 500).json({ message: error.message });
    }
}
        

Error Handling Best Practices:

  1. Repository Layer:
  2. Service Layer:
  3. Controller Layer:

Example of Professional Error Handling Flow:

  1. Controller Layer: Receives the request and calls the service.
  2. Service Layer: Calls the repository and applies business logic.
  3. Repository Layer: Handles low-level errors (database, data not found).

Example Code (Professional Pattern):

// Custom Error Class
class CustomError extends Error {
    constructor(statusCode, message) {
        super(message);
        this.statusCode = statusCode;
        this.name = this.constructor.name;
    }
}

// Repository: Data Access Layer
const userRepo = (userId) => {
    const user = users.find(user => user.id === userId);
    if (!user) {
        throw new CustomError(404, "User not found");
    }
    return user;
}

// Service: Business Logic Layer
const userService = (userId) => {
    const user = userRepo(userId);
    if (user.status !== "active") {
        throw new CustomError(403, "User is not active");
    }
    return user;
}

// Controller: HTTP Request Handler
const userController = (req, res) => {
    const userId = req.params.userId;
    try {
        const user = userService(userId);
        res.status(200).json({ data: user });
    } catch (error) {
        console.error(`Error: ${error.message}`);
        res.status(error.statusCode || 500).json({ message: error.message });
    }
}

// Sample Request: Handling User Not Found
const req = { params: { userId: 999 } };  // Invalid userId
const res = {
    status: (statusCode) => ({ json: (message) => console.log(statusCode, message) })
};
userController(req, res);  // Expected Output: 404 User not found
        

Summary:

  • Repository Layer should handle data access and throw errors related to data issues.
  • Service Layer should focus on business logic and throw errors if the business logic fails (e.g., invalid state, unauthorized).
  • Controller Layer should handle HTTP-specific concerns and format errors into meaningful responses with the appropriate HTTP status codes.

By following this approach, you ensure that each layer in your application is responsible for a specific concern, making the application easier to maintain and extend in the future.

要查看或添加评论,请登录

Mayank Gangwar的更多文章

社区洞察

其他会员也浏览了