Back to Blog
Lesson 13 of the Cloudflare: Cloudflare for Developers: DNS to CDN course
Cloud NativeJuly 14, 20264 min read

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.

CloudflareWorkersHTTPWeb DevelopmentEdge Computing

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.

JAVASCRIPT
export 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.

JAVASCRIPT
export 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.

JAVASCRIPT
export 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

  1. Open your existing project directory from the previous lesson.
  2. Modify your index.js file to check for a custom header named X-Debug-Mode.
  3. If the header is present and equals "true", return a JSON response with a 200 status and a message: {"debug": "enabled"}.
  4. If the header is missing or not "true", return a 403 Forbidden status with the message: {"error": "Access denied"}.
  5. Deploy the changes using npx wrangler deploy and test it using curl -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 the Response object. Some browsers and proxies expect standard casing (e.g., Content-Type instead of content-type), though the HTTP spec is technically case-insensitive.
  • Immutable Requests: The incoming Request object is immutable. If you need to modify headers before passing the request to an origin server, you must create a new Request object using new Request(request, { ... }).
  • Body Consumption: You can only read the body of a Request or Response once. If you need to read it multiple times (e.g., for logging and then proxying), use request.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

Similar Posts