SyntaxStudy
Sign Up
Express.js Streaming Responses and res.locals
Express.js Beginner 1 min read

Streaming Responses and res.locals

`res.locals` is a plain object scoped to a single request-response cycle. Middleware can attach data to it — such as the authenticated user or localisation settings — and all subsequent middleware and route handlers in that cycle can read it without polluting `req`. This is the idiomatic Express way to share request-scoped state. For large payloads, streaming is more memory-efficient than buffering the entire response. You can pipe a Node.js readable stream directly into `res`, or use `res.write()` followed by `res.end()` for chunked transfer encoding. Server-Sent Events and newline-delimited JSON streams are built this way. Setting `Cache-Control` and `ETag` headers correctly with `res.set()` or `res.vary()` is critical for CDN compatibility. Express's `res.format()` performs content negotiation, automatically selecting between JSON, HTML, or text responses based on the `Accept` header sent by the client.
Example
const express = require('express');
const fs      = require('fs');
const path    = require('path');
const app     = express();

// Attach user to res.locals in middleware
app.use((req, res, next) => {
    res.locals.user = { id: 1, role: 'admin' }; // from JWT in real app
    next();
});

// Read res.locals in route handler
app.get('/me', (req, res) => {
    res.json(res.locals.user);
});

// Stream a large file (memory-efficient)
app.get('/stream/log', (req, res) => {
    res.setHeader('Content-Type', 'text/plain');
    const stream = fs.createReadStream(path.join(__dirname, 'app.log'));
    stream.on('error', () => res.status(404).end());
    stream.pipe(res); // pipe readable → writable (response)
});

// Content negotiation with res.format()
app.get('/data', (req, res) => {
    const data = { id: 1, name: 'Widget' };
    res.format({
        'application/json': () => res.json(data),
        'text/html':        () => res.send(`<p>${data.name}</p>`),
        default:            () => res.status(406).send('Not Acceptable'),
    });
});

app.listen(3000);