Custom headers
Making your own headers
The last two lessons covered standard headers that the HTTP spec defines. But sometimes you need to send metadata that no standard header covers. Maybe you want to tell the client how many API requests they have left. Maybe you want to attach a unique request ID for debugging. Maybe you want to include timing information. HTTP lets you add your own custom headers for exactly these situations.
The X- prefix and why it is deprecated
You will see a lot of custom headers that start with X-:
X-Request-Id: req_a1b2c3
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42 The X- prefix was originally meant to mark headers as “non-standard.” The idea was that if everyone prefixed their custom headers with X-, there would be no confusion with official HTTP headers.
The problem? Some X- headers became so popular that they became de facto standards. X-Forwarded-For is a good example. Now there is an awkward situation: do you standardize it as Forwarded-For (breaking all existing software) or keep the X- prefix (making the naming convention meaningless)?
RFC 6648 (published in 2012) deprecated the X- convention. New custom headers should just use a descriptive name without the prefix. But you will still see X- headers everywhere, and that is fine. Existing ones are not going away.
Rate limit headers
One of the most common uses for custom headers is rate limiting. These headers tell the client how they are doing against the API’s rate limits:
X-RateLimit-Limit: 100 -> Maximum requests allowed in this window
X-RateLimit-Remaining: 42 -> Requests remaining before you hit the limit
X-RateLimit-Reset: 1705312800 -> When the window resets (Unix timestamp) These are incredibly useful for clients. Instead of blindly sending requests until they get a 429, they can check their remaining quota and back off proactively.
[!NOTE] The Securing Your API course adds these headers to rate-limited endpoints. The REST API Design course includes them in API response conventions.
Request ID
A request ID is a unique identifier generated by the server for each request:
X-Request-Id: req_a1b2c3d4 Why is this useful? Imagine a user reports a bug: “My request failed.” Without a request ID, your team has to search through millions of log entries to find the right one. With a request ID, the user says “my request req_a1b2c3d4 failed,” and your team can find it instantly. The server includes this ID in all log entries for that request, and the client receives it in the response header.
Timing headers
You can also tell the client how long things took:
Server-Timing: db;dur=12.5, cache;dur=0.2
X-Response-Time: 15ms Server-Timing is actually a standard header that breaks down where time was spent. X-Response-Time is a custom header showing total processing time. Both are useful for performance monitoring.
Setting custom headers on the server
route.get("/books", {
resolve: (c) => {
const requestId = crypto.randomUUID();
const start = Date.now();
const books = db.prepare("SELECT ...").all();
return new Response(JSON.stringify(books), {
headers: {
"Content-Type": "application/json",
"X-Request-Id": requestId,
"X-Response-Time": `${Date.now() - start}ms`,
},
});
},
}); crypto.randomUUID() generates a unique ID for this request. Date.now() before and after the database query gives us the processing time. Both values are included as response headers.
Reading custom headers on the client
const response = await fetch("https://api.example.com/books");
const remaining = response.headers.get("x-ratelimit-remaining");
const requestId = response.headers.get("x-request-id");
if (parseInt(remaining) < 10) {
console.warn("Approaching rate limit");
} One thing to watch out for: this code works when the client and server are on the same origin. But what about cross-origin requests?
[!WARNING] Custom headers in cross-origin responses are not visible to JavaScript by default. The server must explicitly list them in
Access-Control-Expose-Headers:Access-Control-Expose-Headers: X-Request-Id, X-RateLimit-RemainingWithout this,
response.headers.get("x-request-id")returns null, even though the header was sent. The browser hides it. This catches a lot of people off guard.
That wraps up headers. We have covered request headers, response headers, and custom headers. The next section digs into the body of HTTP messages, starting with the format you will use the most: JSON.
Exercises
Exercise 1: Add an X-Request-Id header to every response from your server. Use it to find specific requests in your server logs.
Exercise 2: Add rate limit headers to a response. Read them on the client side and log a warning when the remaining count is low.
Exercise 3: Add Access-Control-Expose-Headers and verify your custom headers are readable from JavaScript in a cross-origin request.
Why was the X- prefix for custom headers deprecated?