expressjs/express
Content negotiation
Active contributors: dougwilson, wesleytodd, ulisesgascon
HTTP content negotiation in Express is built on three pieces:
- Inbound:
req.accepts/req.acceptsEncodings/req.acceptsCharsets/req.acceptsLanguages— backed by theacceptspackage. - Type matching:
req.is(type)— backed by thetype-ispackage. - Outbound:
res.format(obj)— usesreq.acceptsplus thehttp-errorsandmime-typespackages.
Inbound: what does the client want?
app.get('/data', (req, res) => {
switch (req.accepts(['html', 'json', 'xml'])) {
case 'html':
return res.render('data');
case 'json':
return res.json({ ok: true });
case 'xml':
return res.type('xml').send('<ok/>');
default:
return res.status(406).send('Not Acceptable');
}
});The full helpers, all defined in lib/request.js:
req.accepts(types...)— content type match.req.acceptsEncodings(encodings...)—Accept-Encodingmatch.req.acceptsCharsets(charsets...)—Accept-Charsetmatch.req.acceptsLanguages(langs...)—Accept-Languagematch.
Each instantiates a fresh accepts(this) and delegates.
req.is
req.is(type) checks the request Content-Type (not Accept). It's the inverse direction:
if (req.is('json')) {
// body parser will set req.body
}Returns the matched type string, false (no match), or null (no Content-Type at all).
Outbound: res.format
res.format is a one-stop content negotiation helper for handlers. It's roughly equivalent to:
app.get('/users/:id', (req, res) => {
res.format({
'text/plain': () => res.send('user info'),
'text/html': () => res.send('<p>user info</p>'),
'application/json': () => res.json({ user: 'info' }),
default: () => res.status(406).send('Not Acceptable'),
});
});Internally (lib/response.js):
graph TD
Format[res.format obj] --> Keys[keys = without default]
Keys --> Vary["res.vary 'Accept'"]
Vary --> Match[req.accepts keys]
Match --> Found{match?}
Found -- yes --> SetType[set Content-Type from normalizeType]
SetType --> Call[obj key called]
Found -- no --> Default{has default?}
Default -- yes --> RunDefault[obj.default called]
Default -- no --> Err["next createError 406, types"]The Vary: Accept header is added so caches don't conflate responses for different Accept headers.
When neither a matching key nor default is provided, Express forwards an http-errors 406 with a types property listing the canonical MIME types the handler advertised. finalhandler then formats it.
Encoding tricks
res.type('html')— Express normalises'html'to'text/html; charset=utf-8'via themime-typespackage'scontentType()helper.- Charset handling in
res.send— when the body is a string, Express auto-injectscharset=utf-8into the existingContent-Typeif it has none. - JSONP —
res.jsonpis a special case: it forcesContent-Type: text/javascriptand addsX-Content-Type-Options: nosniff.
Related pages
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.