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):
Example:
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):
Example:
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):
领英推荐
Example:
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:
Example of Professional Error Handling Flow:
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:
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.