In this lesson, you will learn how to gracefully pass services—like database connections—into your route handlers using middleware instead of relying on risky global variables.
The Problem with Globals
When building an application, you often need to access a database client or a logger inside your route handlers. A common beginner mistake is to import a database connection from a global file:
// Risky global approach
const db = require('./db-connection');
app.get('/users', (req, res) => {
db.query('SELECT * FROM users'); // Implicit dependency on global state
});
This approach makes your code hard to test and fragile. If you need to switch databases for a test environment or mock a service, you are stuck with whatever that global module exports.
Injecting via Middleware
Dependency Injection (DI) allows us to "inject" these services into the request object (req) as it passes through the middleware chain. By attaching our database instance to req at the start of the lifecycle, every subsequent handler gets access to the exact service it needs without knowing where it came from.
// The Middleware Injector
const injectDb = (dbInstance) => (req, res, next) => {
req.db = dbInstance; // Attach the dependency to the request
next();
};
// Usage
app.use(injectDb(myDatabasePool));
app.get('/users', (req, res) => {
// We use the service injected into the request
req.db.query('SELECT * FROM users')
.then(data => res.json(data));
});
Why This Matters
By attaching services to the req object, we treat our dependencies as part of the "request context." This decouples your route logic from the specific implementation of your services. If you want to use a different database mock during testing, you simply change the instance you pass to the injectDb middleware. Your route handlers remain identical, unaware that the underlying service has changed. This pattern keeps your architecture clean, modular, and much easier to debug as your server scales.
Key Takeaway: Dependency Injection via middleware transforms your route handlers into modular functions that rely on context passed through the request object, rather than hard-coded global dependencies.