nocache
nocache is a tiny Express and Connect middleware factory that writes three fixed response headers: `Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate`, `Expires: 0`, and `Surrogate-Control: no-store`. It then calls the next middleware. The goal is to discourage browsers, shared proxies, and surrogate caches from storing or reusing a response. It has no policy options, route matcher, cache purge mechanism, or response-body logic, so placement in your middleware stack determines exactly which responses it affects.
A clear, low-risk helper when a route truly must not be stored and the fixed policy is exactly right. Do not install it as a substitute for designing cache policy, and do not mount it across cacheable public content by default.
Use it if
- You need a consistent no-store policy on a small set of sensitive Express or Connect routes
- You are temporarily stopping reuse of a changing response while you repair a bad caching policy
- You want a dependency-free middleware with bundled TypeScript declarations rather than repeating three headers in several routers
- You have tests proving no later middleware or CDN configuration overwrites the headers
- You plan to mount it globally on a site with static assets or public GET responses: it disables browser and surrogate reuse and can needlessly increase latency, bandwidth, and origin load
- You need configurable directives such as private, max-age, s-maxage, stale-while-revalidate, or immutable: version 4.0.0 accepts no options and always writes the same three values
- You need to purge an already cached bad response: response headers affect handling of the current response and do not actively invalidate every previously stored browser or CDN object
- Your cache policy lives at a CDN or reverse proxy and can override origin headers: this middleware cannot verify or enforce what the edge actually stores
- You run Node older than 16 or require native ESM packaging: 4.0.0 declares Node >=16 and ships CommonJS with an `export =` declaration
Setup reality
`npm install nocache` installs five files and no runtime dependencies, peer dependencies, native code, environment variables, or configuration. Version 4.0.0 supports Node 16 or newer, ships CommonJS, and includes a TypeScript declaration based on Node's IncomingMessage and ServerResponse types. Call the factory once and mount the returned middleware: `app.use(nocache())`. Calling `app.use(nocache)` is wrong because Express will pass request arguments to the factory, which returns another function instead of finishing the request. The package exposes no options. It always calls `setHeader` for the same Surrogate-Control, Cache-Control, and Expires values, overwriting values set earlier. Middleware or route code that sets those headers later wins, so order is part of correctness. Mounting it globally also affects HTML, JSON, downloads, error pages, and static responses that pass through it; route or router scope is usually safer. It does not remove ETag or Last-Modified, though `no-store` tells compliant caches not to store the response. It does not add Clear-Site-Data, send a CDN purge request, vary by authentication state, or distinguish shared from private caches. A prior response may already exist in a browser or edge cache, and platform-specific CDN rules can ignore or replace origin directives. Test the final response at the public edge, not just inside Express. For sensitive data, cache headers complement transport security, authorization, correct logout behavior, and avoiding secrets in URLs; they do not erase browser history or copies held elsewhere. Because the middleware runs before the response is produced, it also adds the headers to errors generated later in the chain unless an error handler overwrites them.
Patterns
Apply no-store headers to every later routedisable-cache-globally
const express = require('express');
const nocache = require('nocache');
const app = express();
app.use(nocache());
app.get('/account', (req, res) => res.json({ id: req.user.id }));Global mounting also disables caching for public and static responses that pass through it. Prefer narrower scope unless every response is sensitive.
Disable caching on one sensitive routeprotect-sensitive-route
const nocache = require('nocache');
app.get('/account/export', nocache(), async (req, res) => {
const exportData = await buildAccountExport(req.user.id);
res.json(exportData);
});This is usually safer than a global middleware because unrelated assets and public GET endpoints retain their own cache policy.
Apply the policy to an authenticated routerprotect-router
const express = require('express');
const nocache = require('nocache');
const account = express.Router();
account.use(requireUser);
account.use(nocache());
account.get('/profile', showProfile);
account.post('/logout', logOut);
app.use('/account', account);Place authentication and no-cache policy deliberately. Headers can still be present on authentication errors depending on middleware order.
Disable caching only for signed-in usersapply-conditionally
const nocache = require('nocache');
const noCache = nocache();
app.use((req, res, next) => {
if (req.user) return noCache(req, res, next);
next();
});Shared-cache safety also depends on Vary and authentication-aware CDN configuration. Conditional origin headers alone may not repair an unsafe edge cache key.
Set headers on sensitive error responsesprotect-error-responses
const nocache = require('nocache');
app.get('/reset-password', nocache(), async (req, res, next) => {
try {
res.render('reset', await loadResetState(req.query.token));
} catch (error) {
next(error);
}
});The headers remain on downstream errors unless an error handler replaces them, which is useful when error bodies can also contain private data.
Test the final Express headersverify-response-headers
const request = require('supertest');
it('prevents storage of account responses', async () => {
const response = await request(app).get('/account/profile').expect(200);
expect(response.headers['cache-control'])
.toBe('no-store, no-cache, must-revalidate, proxy-revalidate');
expect(response.headers['surrogate-control']).toBe('no-store');
expect(response.headers.expires).toBe('0');
});An application test catches later middleware overwrites. Also inspect the public CDN response because edge configuration can change headers after Express.
Mount the returned middleware, not the factoryavoid-factory-mistake
const nocache = require('nocache');
app.use(nocache()); // correct
// app.use(nocache); // wrongnocache takes no request arguments and returns the real middleware. Forgetting parentheses can leave the request unfinished.
Keep cacheable routes outside its scopepreserve-route-cache-policy
const nocache = require('nocache');
app.use('/private', nocache(), privateRouter);
app.use('/assets', express.static('public', {
maxAge: '1y',
immutable: true,
}));Mount order and path scope keep long-lived asset caching intact. Confirm no parent router applies nocache before both branches.
Understand last-writer-wins header orderoverride-after-nocache
const nocache = require('nocache');
app.get('/status', nocache(), (req, res) => {
res.set('Cache-Control', 'public, max-age=30');
res.json({ ok: true });
});This route overwrites nocache's Cache-Control value but leaves Expires and Surrogate-Control unchanged, producing a contradictory policy. Avoid mixed writers.
Use the middleware in a Connect stackuse-with-connect
const connect = require('connect');
const nocache = require('nocache');
const app = connect();
app.use('/session', nocache());
app.use('/session', (req, res) => {
res.end('private');
});The declaration uses Node IncomingMessage and ServerResponse, so no Express-only response methods are required.
Use three local headers instead of installing itset-headers-without-dependency
function noStore(req, res, next) {
res.setHeader('Surrogate-Control', 'no-store');
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Expires', '0');
next();
}This is the complete 4.0.0 behavior. A local helper is reasonable when adding a package for three fixed lines is not worth supply-chain surface.
Request a browser cache clear on a dedicated responseclear-browser-cache-separately
const nocache = require('nocache');
app.post('/session/logout', nocache(), (req, res) => {
res.setHeader('Clear-Site-Data', '"cache"');
req.session.destroy(() => res.sendStatus(204));
});nocache does not emit Clear-Site-Data. Browser support and scope vary, and this still does not purge CDN objects or replace correct logout logic.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| express-cache-controller | npm | You need Express middleware with configurable Cache-Control directives instead of one fixed no-store policy |
| helmet | npm | You need a broader set of Express security headers and are willing to set cache policy separately |
| fresh | npm | You need to decide whether a request is fresh from ETag and Last-Modified validators rather than disabling caching |
| cache-control-parser | npm | You need to parse and inspect Cache-Control directives as data instead of writing fixed headers |