In this lesson, you will learn how to decouple your business logic from database-specific code using the Repository Pattern to make your application easier to test.
The Problem: Tight Coupling
When you write database queries directly inside your service layer, your business logic becomes "stuck" to a specific implementation. If you use MongoDB, your services are riddled with db.collection.find(). If you ever decide to switch to PostgreSQL or need to test your service without a live database, you have to rewrite your business logic.
The Repository Pattern introduces an abstraction layer—a "middleman"—that handles all data access, presenting a clean interface to the rest of your application.
The Repository Interface
Think of the Repository as a collection of domain objects in memory. Your service layer doesn't need to know if the data comes from a JSON file, a Redis cache, or a SQL database. It only knows that it can ask the repository for data.
Here is how you might abstract a UserRepository:
// The Interface (Contract)
class UserRepository {
async getById(id) { throw new Error("Not implemented"); }
}
// The Implementation (e.g., MongoDB)
class MongoUserRepository extends UserRepository {
async getById(id) {
return await db.users.findOne({ _id: id });
}
}
Why This Matters for Testing
Because your UserService now relies on an instance of UserRepository (via dependency injection) rather than a direct database connection, you can easily swap the MongoUserRepository for a MockUserRepository during unit tests.
// A simple mock for testing
class MockUserRepository {
async getById(id) {
return { id, name: "Test User" };
}
}
// Your service remains unchanged!
const service = new UserService(new MockUserRepository());
By abstracting data access, your services focus strictly on business rules, while the repository acts as the gatekeeper for data persistence. This creates a clear boundary, allowing you to swap storage backends or mock entire data layers without touching your core application logic.
Key Takeaway: The Repository Pattern shields your business logic from database specifics, allowing you to swap storage engines or inject mock data for seamless, isolated testing.