Open-Source Wikis

/

Express

/

Features

/

Content negotiation

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 the accepts package.
  • Type matching: req.is(type) — backed by the type-is package.
  • Outbound: res.format(obj) — uses req.accepts plus the http-errors and mime-types packages.

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-Encoding match.
  • req.acceptsCharsets(charsets...)Accept-Charset match.
  • req.acceptsLanguages(langs...)Accept-Language match.

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 the mime-types package's contentType() helper.
  • Charset handling in res.send — when the body is a string, Express auto-injects charset=utf-8 into the existing Content-Type if it has none.
  • JSONPres.jsonp is a special case: it forces Content-Type: text/javascript and adds X-Content-Type-Options: nosniff.
  • Request — inbound helpers.
  • Responseres.format, res.type, res.json, res.jsonp.
  • Glossary — "JSONP", "fresh / stale".

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Content negotiation – Express wiki | Factory