Working with Content Negotiation

1. Checking Accept Header

APIReturns
req.accepts(types)Best match or false
req.acceptsCharsets()Charset negotiation
req.acceptsEncodings()Compression preferences
req.acceptsLanguages()i18n preferences

Example: Manual negotiation

app.get("/data", (req, res) => {
  switch (req.accepts(["json", "html", "xml"])) {
    case "json": return res.json(data);
    case "html": return res.render("data", { data });
    case "xml":  return res.type("xml").send(toXml(data));
    default:     return res.status(406).send("Not Acceptable");
  }
});

2. Responding with JSON

Example: Standard envelope

res.json({
  data: items,
  meta: { page, limit, total },
  links: { next: "/items?page=" + (page + 1) }
});

3. Responding with HTML

MethodUse
res.send(html)Inline HTML string
res.render(view)Template engine
res.sendFile(path)Pre-built HTML file

4. Responding with XML

Example: XML response

import { create } from "xmlbuilder2";
res.type("application/xml").send(
  create({ user: { id: user.id, name: user.name } }).end({ prettyPrint: true })
);

5. Using res.format() for Multiple Types

Example: Format dispatch

app.get("/users/:id", (req, res) => {
  const user = getUser(req.params.id);
  res.format({
    "application/json": () => res.json(user),
    "text/html":        () => res.render("user", { user }),
    "text/plain":       () => res.send(`${user.id}: ${user.name}`),
    default:            () => res.status(406).send("Not Acceptable")
  });
});
Note: res.format() automatically sets Vary: Accept for proper caching.

6. Setting Default Content Type

res.type()Result Content-Type
"json"application/json
"html"text/html; charset=utf-8
"text"text/plain; charset=utf-8
".pdf"application/pdf
"image/png"image/png

7. Handling Unsupported Types

Example: 406 Not Acceptable

const accepted = req.accepts(["json", "html"]);
if (!accepted) return res.status(406).json({
  error: "Not Acceptable",
  supported: ["application/json", "text/html"]
});

8. Implementing Custom Formatters

Example: CSV formatter

function toCsv(rows) {
  if (!rows.length) return "";
  const headers = Object.keys(rows[0]);
  const escape = (v) => `"${String(v).replace(/"/g, '""')}"`;
  return [headers.join(","), ...rows.map(r => headers.map(h => escape(r[h])).join(","))].join("\n");
}

res.format({
  "application/json": () => res.json(rows),
  "text/csv": () => res.attachment("export.csv").type("csv").send(toCsv(rows))
});

9. Parsing Multiple Content Types

Example: Multi-type body parsing

app.use(express.json({ type: ["application/json", "application/*+json"] }));
app.use(express.urlencoded({ extended: true }));
app.use(express.text({ type: "text/csv" }));

10. Setting charset in Content-Type

PatternResult
res.type("html")text/html; charset=utf-8 (auto)
res.set("Content-Type", "text/html; charset=iso-8859-1")Explicit override
res.charset = "utf-8"Manual property
Warning: Always use utf-8 for new APIs. Mixing charsets between layers (DB, app, transport) causes mojibake.