Content Negotiation

Serve JSON or Other Formats Using ctx.accepts() and ctx.is()

Content Negotiation

Use Koa's built-in ctx.accepts() and ctx.is() helpers to inspect Accept and Content-Type headers and respond with the right format or a 406 Not Acceptable.

3 min read Level 2/5 #koa#content-negotiation#accept
What you'll learn
  • Inspect the Accept header with ctx.accepts() to choose a response format
  • Check the request Content-Type with ctx.is() before parsing
  • Return 406 when the server cannot satisfy the client's Accept header

HTTP content negotiation lets a single endpoint respond in multiple formats depending on what the client declares it can accept. The client sends an Accept header; the server inspects it and picks the best match.

ctx.accepts()

ctx.accepts() reads the Accept request header and returns the best matching type from the list you provide, or false if none match.

router.get("/articles/:id", async (ctx) => {
  const article = await ArticleService.findById(ctx.params.id);

  const type = ctx.accepts("json", "text/plain");

  if (type === "json") {
    ctx.type = "application/json";
    ctx.body = article;
  } else if (type === "text/plain") {
    ctx.type = "text/plain";
    ctx.body = `${article.title}\n\n${article.body}`;
  } else {
    ctx.throw(406, "Not Acceptable — supported: application/json, text/plain");
  }
});

When the client sends Accept: application/json (or omits the header entirely), Koa returns "json". If the client sends Accept: application/xml and you do not list XML, ctx.accepts() returns false and you should respond 406.

ctx.is()

ctx.is() checks the Content-Type of the incoming request body — useful for POST/PUT routes that may receive either JSON or form data:

router.post("/articles", async (ctx) => {
  if (!ctx.is("application/json")) {
    ctx.throw(415, "Unsupported Media Type — send application/json");
  }
  const { title, body } = ctx.request.body;
  ctx.status = 201;
  ctx.body = await ArticleService.create({ title, body });
});

ctx.is() accepts mime-type strings and wildcards ("json" matches application/json; "*/json" matches any +json variant).

Setting the Response Type

Always set ctx.type explicitly when returning non-JSON so Koa sends the correct Content-Type response header:

ctx.type = "application/json";   // default for plain objects
ctx.type = "text/csv";
ctx.type = "application/xml";

Negotiation in Practice

Accept headerctx.accepts(“json”,“text”)Status
application/json”json”200
text/plain”text”200
(omitted)“json”200 — first type wins
application/xmlfalse406

Up Next

Versioning an API lets you evolve the contract without breaking existing clients.

API Versioning →