Working with Response Locals
1. Understanding res.locals Object
| Property | Lifetime | Default |
res.locals | Single request | {} |
app.locals | Process lifetime | {settings, mountpath, ...} |
2. Setting Request-Scoped Variables
Example: Attach user
app.use(async (req, res, next) => {
res.locals.user = await getCurrentUser(req);
res.locals.requestId = req.id;
next();
});
3. Sharing Data Between Middleware
Example: Pipeline state
app.use((req, res, next) => { res.locals.startTime = Date.now(); next(); });
app.use((req, res, next) => { res.locals.tenant = req.subdomains[0]; next(); });
app.get("/me", (req, res) => res.json({
...res.locals.user,
tenant: res.locals.tenant,
ms: Date.now() - res.locals.startTime
}));
| Pattern | Why |
res.locals | Available in templates & downstream middleware |
req.user (Passport) | Convention for auth result |
| AsyncLocalStorage | Cross-await context (Node 16+) |
4. Passing Data to Views
Example: Implicit template context
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
res.locals.flash = req.session.flash;
next();
});
app.get("/", (req, res) => res.render("home")); // csrfToken & flash auto-available
5. Using res.locals vs app.locals
| Use Case | Where |
| Site name, version | app.locals |
| Helper functions (formatDate) | app.locals |
| Current user, flash | res.locals |
| Per-request feature flags | res.locals |
6. Implementing User Context
Example: User middleware
export async function userContext(req, res, next) {
try {
const token = req.cookies.session;
if (token) {
const { sub } = jwt.verify(token, env.JWT_SECRET);
res.locals.user = await db.users.findById(sub);
}
next();
} catch { next(); }
}
7. Creating Flash Messages
Example: One-shot messages
app.use((req, res, next) => {
res.locals.flash = req.session.flash || [];
delete req.session.flash; // consume
next();
});
// To set
req.session.flash = [{ type: "success", text: "Saved!" }];
res.redirect("/dashboard");
| Library | Notes |
connect-flash | Standard middleware (requires sessions) |
| Manual session | Above pattern |
res.locals.pagination = { page, limit, total };
res.json({
data: results,
meta: res.locals.pagination
});
9. Using res.locals for Error Context
Example: Error template context
app.use((err, req, res, next) => {
res.locals.error = {
message: err.message,
status: err.status || 500,
stack: process.env.NODE_ENV === "development" ? err.stack : undefined
};
res.status(res.locals.error.status).render("error");
});
10. Cleaning Up res.locals
| Question | Answer |
| Auto-cleared? | Yes — new res.locals per request |
| Memory leak risk? | None unless you store on app.locals or external structures |
| Avoid storing | Don't put DB connections, secrets, large buffers |
Warning: Never store the full req or res in res.locals — circular references break templates and JSON serialization.