hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses URL mastery with @hectoday/http

Understanding URLs

  • What is a URL?
  • The URL constructor
  • URL properties deep dive

URLSearchParams

  • URLSearchParams basics
  • Modifying search params
  • Iterating over params
  • URL and SearchParams together

Encoding

  • Encoding and special characters

Inside @hectoday/http

  • How @hectoday/http parses queries
  • Routing and URL patterns
  • The Request object and URLs
  • Building API URLs
  • Input validation and query schemas

Putting it all together

  • Capstone: bookmarks API

The Request object and URLs

@hectoday/http is built on Web Standards. That means it uses the browser’s built-in Request and Response classes, the same ones used by fetch(). Let’s understand how Request connects to everything we’ve learned about URLs, and trace the full journey a request takes through the framework.

What is the Request object?

Request is a Web Standard class that represents an HTTP request. You’ve probably already used it indirectly with fetch():

// When you call fetch(), you're creating a Request behind the scenes
fetch("https://api.example.com/users");

// This is equivalent to:
const request = new Request("https://api.example.com/users");
fetch(request);

A Request bundles together everything about an HTTP request: the URL, the method, headers, and body. It’s a single object that carries all the information needed to describe “what the client wants.”

The URL lives inside the Request

Every Request has a .url property containing the full URL as a string:

const request = new Request("https://api.example.com/users?page=2");

request.url; // "https://api.example.com/users?page=2"
request.method; // "GET" (the default)

But .url is just a string. To actually work with the URL (read the pathname, get query params, extract the hostname), you parse it with the URL constructor:

const url = new URL(request.url);

url.pathname; // "/users"
url.searchParams.get("page"); // "2"

This is exactly what @hectoday/http does on every incoming request:

// From setup.ts:
const url = new URL(request.url);
const matched = findRoute(router, request.method, url.pathname);
const rawQuery = parseQuery(url.search);

Three steps: parse request.url into a URL object, use url.pathname for routing, use url.search for query parsing. Every concept from this course shows up in these three lines.

Creating requests with different methods

The second argument to new Request() lets you specify the HTTP method, headers, and body:

// GET request (default)
new Request("https://api.com/users");

// POST request with a JSON body
new Request("https://api.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Alice" }),
});

// DELETE request
new Request("https://api.com/users/42", {
  method: "DELETE",
});

When you don’t specify a method, it defaults to "GET". The body is only relevant for methods like POST, PUT, and PATCH. A GET or DELETE request typically has no body.

The Response object

Just as Request represents what comes in, Response represents what goes out. In @hectoday/http, your resolve function must return a Response:

route.get("/hello", {
  resolve: (c) => {
    return Response.json({ message: "Hello!" });
  },
});

Response.json() is a static helper method that does three things: it JSON.stringifys the data, sets the Content-Type header to application/json, and wraps it all in a new Response object. It’s a shortcut you’ll use constantly for API responses.

Common ways to create responses

// JSON response (most common for APIs)
Response.json({ data: "value" });

// JSON with a custom status code
Response.json({ error: "Not found" }, { status: 404 });

// Plain text
new Response("Hello, world!");

// Empty response with status 204 (No Content)
new Response(null, { status: 204 });

// Response with custom headers
new Response("data", {
  status: 200,
  headers: {
    "Content-Type": "text/plain",
    "X-Custom": "value",
  },
});

Notice the pattern. Response.json() is a shortcut for the most common case. For everything else, you use new Response() directly and pass the body as the first argument and options (status, headers) as the second.

The full request lifecycle

Here’s the complete flow of a request through @hectoday/http, step by step:

1. Request comes in
   ↓
2. onRequest hook (optional)            ← set up shared state ("locals")
   ↓
3. new URL(request.url)                 ← parse the URL
   ↓
4. findRoute(router, method, pathname)  ← match the route
   ↓  (if no match → onNotFound hook → response)
5. parseQuery(url.search)               ← parse query string into object
   ↓
6. matched.params                       ← extract path params from rou3
   ↓
7. request.text() → JSON.parse          ← parse body (if body schema defined)
   ↓
8. runValidation(schemas, ...)           ← validate with Zod (if schemas defined)
   ↓
9. resolve(context)                      ← call your handler
   ↓
10. onResponse hook (optional)           ← modify response (e.g., add CORS headers)
    ↓
11. Response returned                    ← sent back to client

Every step uses Web Standards. There’s no custom request or response abstraction. It’s the same Request and Response you’d use with fetch() in a browser.

Look at steps 3 through 6. That’s URL anatomy, the URL constructor, routing by pathname, and query parsing with parseQuery(). Everything you’ve learned in this course so far feeds into this lifecycle.

Why Web Standards matter

Because @hectoday/http uses standard Request and Response, your app works everywhere these standards are supported:

  • Node.js (v18+)
  • Deno
  • Bun
  • Cloudflare Workers
  • Vercel Edge Functions

The fetch handler signature (request: Request) => Response is the universal interface for HTTP servers in the JavaScript ecosystem. Any platform that supports this interface can run your @hectoday/http app without changes. You write your code once, and it runs on any runtime that speaks web standards.

In the next lesson, we’ll look at how @hectoday/http builds these Request objects for you when you use app.request() for testing.

What does @hectoday/http use new URL(request.url) for?

← Routing and URL patterns Building API URLs →

© 2026 hectoday. All rights reserved.