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.
| Approach | Case-sensitive? | Returns | Best for |
|---|---|---|---|
req.headers | Yes (keys are lowercased) | Whole object or a value | Iterating over all headers, logging |
req.get(name) | No | Single header value or undefined | Reading one named header |
req.header(name) | No | Alias of req.get | Same as req.get |
Tip: Prefer
req.get(name)in application code. Reserve directreq.headersaccess 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.
| Header | Purpose | Typical value |
|---|---|---|
Authorization | Credentials for the request | Bearer eyJhbGci... |
Content-Type | Media type of the request body | application/json |
Accept | Media types the client can handle | application/json, text/html |
User-Agent | Identifies the client software | Mozilla/5.0 ... |
Host | Target host and port | api.example.com |
X-Request-Id | Correlation ID for tracing | a1b2c3d4 |
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:
| Method | Inspects header | Example |
|---|---|---|
req.accepts(types) | Accept | req.accepts("json") |
req.acceptsEncodings(...) | Accept-Encoding | req.acceptsEncodings("gzip", "br") |
req.acceptsCharsets(...) | Accept-Charset | req.acceptsCharsets("utf-8") |
req.acceptsLanguages(...) | Accept-Language | req.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 theAcceptheader by hand. - Respond with
406 Not Acceptablewhen no offered format matches, and415 Unsupported Media Typewhen the body’sContent-Typeis wrong. - Treat
req.headersas read-only; pass derived data downstream via customreqproperties. - Never log raw
AuthorizationorCookieheaders — redact credentials before they reach your logs.