Content Negotiation

Same Endpoint, Multiple Formats

Content Negotiation

Return JSON to API clients, HTML to browsers, CSV to spreadsheets — all from the same URL.

3 min read Level 2/5 #express#http#content-type
What you'll learn
  • Use req.accepts() and res.format()
  • Choose between formats
  • Send a 406 when nothing matches

A single endpoint can serve multiple representations. The client says what it wants via Accept; the server picks the best match.

req.accepts()

app.get("/users/42", (req, res) => {
  if (req.accepts("html")) {
    return res.render("user", { user });
  }
  if (req.accepts("json")) {
    return res.json(user);
  }
  res.status(406).end();
});

req.accepts(type) returns the matched MIME type (or false).

res.format() — The Cleaner API

app.get("/reports/q3", (req, res) => {
  const report = await fetchReport();

  res.format({
    "application/json": () => res.json(report),
    "text/csv":          () => {
      res.set("content-type", "text/csv");
      res.send(toCSV(report));
    },
    "text/html":         () => res.render("report", { report }),
    default:             () => res.status(406).end(),
  });
});

Express picks the handler that best matches the request’s Accept.

When To Use It

  • APIs serving both browsers and clients — JSON for clients, HTML pages for browsers
  • Reports — same URL gives JSON, CSV, PDF
  • Backwards compatibility — old clients ask for XML, new for JSON

When Not To

For pure JSON APIs, content negotiation is overkill. Just return JSON.

req.is() — Inbound Content-Type

req.accepts() checks what the client wants. req.is() checks what the client sent:

app.post("/upload", (req, res) => {
  if (req.is("multipart/form-data")) {
    return handleMultipart(req, res);
  }
  if (req.is("application/json")) {
    return handleJSON(req, res);
  }
  res.status(415).end();   // Unsupported Media Type
});

Status Codes To Know

  • 406 Not Acceptable — we don’t have what the client asked for
  • 415 Unsupported Media Type — we don’t accept what the client sent
API Versioning →