How to Use Repository Pattern in Express for Clean Code
repository pattern in Express by creating a separate repository class that handles data operations like fetching or saving. Then, call this repository from your route handlers or services to keep your code organized and easier to maintain.Syntax
The repository pattern involves creating a repository class that has methods for data operations like findAll(), findById(), create(), and update(). Your Express routes then call these methods instead of directly accessing the database.
This separates concerns: the repository handles data, and the routes handle HTTP logic.
class UserRepository { constructor(db) { this.db = db; } async findAll() { return await this.db.query('SELECT * FROM users'); } async findById(id) { return await this.db.query('SELECT * FROM users WHERE id = ?', [id]); } async create(user) { return await this.db.query('INSERT INTO users SET ?', user); } async update(id, user) { return await this.db.query('UPDATE users SET ? WHERE id = ?', [user, id]); } } // Usage in Express route app.get('/users', async (req, res) => { const users = await userRepository.findAll(); res.json(users); });
Example
This example shows a simple Express app using a UserRepository class to fetch users from a mock database. The route calls the repository method to get data, keeping the route clean and focused on HTTP handling.
import express from 'express'; // Mock database object with query method const db = { users: [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ], async query(sql, params) { if (sql.startsWith('SELECT * FROM users WHERE id =')) { const id = params[0]; return this.users.filter(user => user.id === id); } if (sql.startsWith('SELECT * FROM users')) { return this.users; } return []; } }; class UserRepository { constructor(db) { this.db = db; } async findAll() { return await this.db.query('SELECT * FROM users'); } async findById(id) { return await this.db.query('SELECT * FROM users WHERE id = ?', [id]); } } const app = express(); const userRepository = new UserRepository(db); app.get('/users', async (req, res) => { const users = await userRepository.findAll(); res.json(users); }); app.get('/users/:id', async (req, res) => { const user = await userRepository.findById(Number(req.params.id)); if (user.length === 0) { return res.status(404).json({ message: 'User not found' }); } res.json(user[0]); }); app.listen(3000, () => { console.log('Server running on http://localhost:3000'); });
Common Pitfalls
1. Mixing data logic in routes: Putting database queries directly inside Express routes makes code hard to maintain.
2. Not handling async errors: Forgetting to catch errors in async repository methods can crash your app.
3. Tight coupling: Avoid directly importing database clients in routes; use repositories to keep code flexible.
/* Wrong way: database query inside route */ app.get('/users', async (req, res) => { const users = await db.query('SELECT * FROM users'); // Direct DB call res.json(users); }); /* Right way: use repository */ app.get('/users', async (req, res) => { try { const users = await userRepository.findAll(); res.json(users); } catch (error) { res.status(500).json({ message: 'Server error' }); } });
Quick Reference
- Create a repository class with methods for data operations.
- Inject or pass your database connection to the repository.
- Call repository methods from Express routes or services.
- Handle errors inside repository or routes to avoid crashes.
- Keep routes focused on HTTP logic, not data access.