Workers foundations
Handle the first request
Read a standard Request and return JSON from the module Worker fetch handler using explicit status and headers.
A module Worker is an object with a fetch method. Cloudflare calls that method once per incoming HTTP request and sends back whatever Response you return.
Let’s replace the starter code with a health route. We parse the URL once, answer /api/health, and return a real 404 for everything else:
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url)
if (url.pathname === '/api/health') {
return Response.json({ ok: true })
}
return Response.json({ error: 'Not found' }, { status: 404 })
}
}
Response.json() serializes the object and sets content-type: application/json for you. Passing { status: 404 } as the second argument sets the status code.
The three inputs
fetch receives three arguments: request, env, and ctx. We only used the first one here, but all three matter.
request is user-controlled data. Every header, every path segment, every byte of the body came from someone you don’t trust.
env holds your bindings, the resources you declared in wrangler.jsonc. We add the first one in a few lessons.
ctx is the execution context. It lets you keep a promise running after the response is sent, which we use later for cache invalidation.
Keep module scope boring
Anything you write outside the fetch method runs when the isolate starts, not when a request arrives. Constants are fine there. Network calls and binding access are not. They can run before any request exists, and the result gets reused across requests in ways you did not plan for.
Test it with curl
Start npm run dev and send two requests, one for the route and one for a path that doesn’t exist:
curl -i http://localhost:8787/api/health
curl -i http://localhost:8787/missing
The first prints HTTP/1.1 200 OK, content-type: application/json, and {"ok":true}. The second prints HTTP/1.1 404 Not Found with {"error":"Not found"}.
Notice the -i flag. It shows the headers. Checking only the body hides the most common mistake, a 200 status on an error response.
Habits worth starting now
Return a response on every code path. A fetch that falls through without returning gives you a confusing runtime error instead of a clean 404.
Distinguish client errors from server errors. Bad input is a 4xx. Your bug or a failing dependency is a 5xx.
Parse a request body only on routes and methods that need one, and enforce a size and shape before trusting it. A GET that tries to read a body is a smell.
Attach a request ID to every response and to every log line. When a user reports a failure, that ID is the thread you pull to find the exact invocation.
Try it on your project: add the health route, restart the dev server, and run both curl commands. Compare the headers you see against the two outputs above.
Lesson completed