Skip to content
Express.js ex requests 4 min read

Reading Request Headers

HTTP headers carry the metadata that surrounds every request: who is calling, what format they expect back, what credentials they hold, and how the payload is encoded. Express gives you two ways to read them — the raw req.headers object and the convenience accessor req.get() — plus a small family of helpers for content negotiation. Getting headers right is the difference between an API that quietly misbehaves and one that responds correctly to every client.

req.headers vs req.get(name)

req.headers is a plain JavaScript object containing every header the client sent. Its keys are always lowercased by Node’s HTTP parser, regardless of the casing the client used, so you must index it in lowercase: req.headers["content-type"], never req.headers["Content-Type"].

req.get(name) (aliased as req.header(name)) is the idiomatic accessor. It is case-insensitive, so any casing you pass resolves to the same value, making it more robust and self-documenting than bracket access.

const express = require("express");
const app = express();

app.get("/inspect", (req, res) => {
  // Both of these read the same header:
  const viaObject = req.headers["user-agent"];
  const viaGetter = req.get("User-Agent"); // case-insensitive

  res.json({ viaObject, viaGetter, match: viaObject === viaGetter });
});

app.listen(3000);

Output:

{
  "viaObject": "curl/8.4.0",
  "viaGetter": "curl/8.4.0",
  "match": true
}

The table below summarizes when to reach for each.

ApproachCase-sensitive?ReturnsBest for
req.headersYes (keys are lowercased)Whole object or a valueIterating over all headers, logging
req.get(name)NoSingle header value or undefinedReading one named header
req.header(name)NoAlias of req.getSame as req.get

Tip: Prefer req.get(name) in application code. Reserve direct req.headers access for cases where you genuinely need the full set — middleware that logs or proxies the entire header collection, for example.

Common headers you will read

A handful of headers come up constantly. Most carry a single string value; a few (Referer, Set-Cookie on responses) have special handling.

HeaderPurposeTypical value
AuthorizationCredentials for the requestBearer eyJhbGci...
Content-TypeMedia type of the request bodyapplication/json
AcceptMedia types the client can handleapplication/json, text/html
User-AgentIdentifies the client softwareMozilla/5.0 ...
HostTarget host and portapi.example.com
X-Request-IdCorrelation ID for tracinga1b2c3d4

Reading credentials from the Authorization header is one of the most common cases. Here is a small auth middleware that extracts a bearer token:

function requireAuth(req, res, next) {
  const auth = req.get("Authorization");
  if (!auth || !auth.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing bearer token" });
  }
  const token = auth.slice("Bearer ".length).trim();
  req.token = token; // hand the token to downstream handlers
  next();
}

app.get("/me", requireAuth, async (req, res) => {
  const user = await verifyToken(req.token);
  res.json(user);
});

Because req.get() returns undefined for headers the client did not send, guard every read — never assume a header is present.

Content negotiation with req.accepts

The Accept header tells the server which response formats the client prefers, in priority order. Rather than parsing that string yourself, Express exposes req.accepts() and its siblings to pick the best match.

req.accepts(types) takes one or more types (or extensions) and returns the best match for the client, honoring the quality values (q=) in the header. If nothing matches, it returns false.

app.get("/report", (req, res) => {
  // Offer JSON and HTML; let the client choose.
  switch (req.accepts(["json", "html"])) {
    case "json":
      return res.json({ status: "ok", format: "json" });
    case "html":
      return res.type("html").send("<h1>Report</h1>");
    default:
      // Client accepts neither
      return res.status(406).send("Not Acceptable");
  }
});

For a request with Accept: text/html, application/json;q=0.9, req.accepts(["json", "html"]) returns "html" because the client weights HTML higher.

Output (request with Accept: application/json):

{ "status": "ok", "format": "json" }

Express provides parallel helpers for the other negotiation headers:

MethodInspects headerExample
req.accepts(types)Acceptreq.accepts("json")
req.acceptsEncodings(...)Accept-Encodingreq.acceptsEncodings("gzip", "br")
req.acceptsCharsets(...)Accept-Charsetreq.acceptsCharsets("utf-8")
req.acceptsLanguages(...)Accept-Languagereq.acceptsLanguages("en", "fr")

There is also req.is(type), which checks the request’s Content-Type and is handy for routing logic based on the inbound payload:

app.post("/upload", (req, res) => {
  if (req.is("application/json")) {
    return res.json({ parsed: req.body });
  }
  if (req.is("multipart/*")) {
    return res.status(415).json({ error: "Use the file endpoint" });
  }
  res.status(415).json({ error: "Unsupported Content-Type" });
});

Express 5 notes

Header handling is essentially unchanged from Express 4 to 5: req.headers, req.get(), req.accepts(), and req.is() all keep the same signatures and behavior. The main difference in Express 5 is that several request properties became read-only getters, so you should treat req.headers as a value to read rather than mutate. If you need to add or rewrite a header for downstream middleware, set a custom property on req (as in the auth example above) instead of editing req.headers.

Best Practices

  • Use req.get(name) for single-header reads so casing never trips you up.
  • Always guard for undefined — clients omit optional headers, and missing values should not throw.
  • Drive content negotiation with req.accepts() rather than parsing the Accept header by hand.
  • Respond with 406 Not Acceptable when no offered format matches, and 415 Unsupported Media Type when the body’s Content-Type is wrong.
  • Treat req.headers as read-only; pass derived data downstream via custom req properties.
  • Never log raw Authorization or Cookie headers — redact credentials before they reach your logs.
Last updated June 14, 2026
Was this helpful?