Handling HTTP Requests: A Developer’s Guide to Workers
Master HTTP request handling in Cloudflare Workers. Learn to access headers, modify response bodies, and return custom status codes for full control at the edge.
Previously in this course, we covered writing your first worker and deploying it to the global network. Now that you have a functional script running at the edge, it is time to move beyond returning a static "Hello World." To build real-world applications, you must master the Request and Response objects.
In the Cloudflare Workers runtime, every interaction follows a simple pattern: your code receives an incoming Request object and must return a Response object. This request-response cycle is the foundation of everything you will build as you progress toward the dynamic backend later in this course.
Accessing HTTP Request Headers
The Request object contains everything you need to know about the incoming traffic, including the URL, the HTTP method (GET, POST, etc.), and—crucially—the headers. Headers provide context, such as the user's browser, the content type being sent, or custom authentication tokens.
To access headers, you use the request.headers object, which implements the standard Headers Web API.
JAVASCRIPTexport default { async fetch(request, env, ctx) { // Access a specific header const userAgent = request.headers.get("user-agent"); // Log it to the console for debugging console.log(CE9178">`Request received from: ${userAgent}`); return new Response("Headers processed!"); }, };
Using .get() is case-insensitive, which is a massive help when dealing with varying client implementations. Always remember that headers are a map; if you need to iterate through all of them, you can use for (const [key, value] of request.headers.entries()).
Modifying the Response Body
Returning a simple string is fine for testing, but real APIs need to return structured data, typically JSON. The Response constructor allows you to pass a body and an options object.
When you modify the response, you are essentially telling the client how to interpret the data you are sending back.
JAVASCRIPTexport default { async fetch(request, env, ctx) { const data = { message: "Hello from the Edge!", status: "success" }; // Return a JSON response return new Response(JSON.stringify(data), { headers: { "content-type": "application/json;charset=UTF-8", }, }); }, };
By setting the content-type header to application/json, you ensure that the browser or API client parses the body correctly rather than treating it as plain text.
Returning Custom HTTP Status Codes
By default, a Response returns a 200 OK status. However, your edge logic often needs to communicate errors or specific states, such as 404 Not Found or 401 Unauthorized. You can set these status codes via the second argument of the Response constructor.
Worked Example: A Conditional API Endpoint
Let's build a simple interceptor that checks for an "Authorization" header and returns a custom status code if it's missing.
JAVASCRIPTexport default { async fetch(request, env, ctx) { const authHeader = request.headers.get("Authorization"); // Check if the header exists if (!authHeader) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } }); } // If authorized, return success return new Response(JSON.stringify({ data: "Top secret content" }), { status: 200, headers: { "content-type": "application/json" } }); }, };
Hands-on Exercise
- Open your existing project directory from the previous lesson.
- Modify your
index.jsfile to check for a custom header namedX-Debug-Mode. - If the header is present and equals
"true", return a JSON response with a200status and a message:{"debug": "enabled"}. - If the header is missing or not
"true", return a403 Forbiddenstatus with the message:{"error": "Access denied"}. - Deploy the changes using
npx wrangler deployand test it usingcurl -H "X-Debug-Mode: true" <your-worker-url>.
Common Pitfalls
- Case Sensitivity: While
request.headers.get()is case-insensitive, be careful when setting headers in theResponseobject. Some browsers and proxies expect standard casing (e.g.,Content-Typeinstead ofcontent-type), though the HTTP spec is technically case-insensitive. - Immutable Requests: The incoming
Requestobject is immutable. If you need to modify headers before passing the request to an origin server, you must create a newRequestobject usingnew Request(request, { ... }). - Body Consumption: You can only read the body of a
RequestorResponseonce. If you need to read it multiple times (e.g., for logging and then proxying), userequest.clone().
FAQ
Q: Can I return HTML instead of JSON?
A: Yes. Just set the content-type header to text/html and provide an HTML string as the body.
Q: Why do I get a 500 error when my code fails?
A: If your worker throws an unhandled exception, it returns a 500 error. Always wrap your logic in try/catch blocks if you are performing external fetches.
Q: Is there a limit to how many headers I can set? A: While there is no hard limit on the number of headers, keep in mind that total header size (including cookies) is limited by the underlying HTTP protocol and Cloudflare's request limits.
Recap
In this lesson, you learned that the Request and Response objects are the primary interface for your edge logic. You now know how to inspect incoming headers, construct JSON responses, and signal status changes with HTTP codes. These skills are essential for the more complex routing and security header implementation we will cover soon.
Up next: Dynamic Header Manipulation
Work with me

Custom Email & File Storage System on Cloudflare (Google Workspace Alternative)
Your own private email + file storage suite on your domain — unlimited mailboxes, no per-seat fees. A self-owned Google Workspace alternative for a flat ~$5/month.

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.


