What is a cookie?
Our login route works. The user types their password, the server verifies it, and then… nothing. The server forgets, the next request arrives anonymous, and we are stuck. We ended the last lesson with this exact gap, and now we start closing it. The first piece of the puzzle is a tiny, ancient, surprisingly simple technology that every browser has built in: the cookie. If you have only used cookies from the outside (“this site needs cookies enabled”), this lesson is where they finally make sense mechanically. You will see that they are much less magical than they look.
A cookie is just a piece of text
Strip away everything you have ever heard about cookies, tracking, GDPR banners, and the scary internet news stories. At its core:
A cookie is a piece of text that a server asks a browser to store. Once stored, the browser automatically sends it back to the server with every subsequent request.
That is it. That is the whole thing. The server does not have to ask for it every time. The browser just attaches it on its own, in the background, without any code running.
A cookie is basically the browser’s way of carrying a little note from the server back to the same server, on every future request, automatically. Same note, every time.
How cookies actually work
The lifecycle has exactly two steps, and each one is just a regular HTTP header.
Step 1: The server sets the cookie
When the server responds to a request, it can include a Set-Cookie header:
HTTP/1.1 200 OK
Set-Cookie: session=abc123
Content-Type: application/json
{"message": "Login successful"} The browser receives this response, notices the Set-Cookie header, and stores the cookie. The cookie has a name (session) and a value (abc123).
Step 2: The browser sends it back, automatically, on every request
On every subsequent request to the same server, the browser tacks the cookie onto the request using a header called Cookie:
GET /dashboard HTTP/1.1
Host: example.com
Cookie: session=abc123 The server reads the Cookie header, sees session=abc123, and knows this request has that cookie attached.
Here is the part that often surprises people: no JavaScript runs to make this happen. The browser does it without any code on the page doing anything. It is a built-in, low-level browser behavior. Any request to the same origin automatically includes all relevant cookies, whether it comes from a link click, a form submission, a fetch call, an image load, whatever.
What is an origin, by the way? It is the combination of the scheme (like http or https), the hostname (like example.com), and the port (like 3000). So http://localhost:3000 is one origin, https://example.com is another, and https://example.com:8080 is yet another. Cookies set by one origin only get sent back to that same origin. A cookie set by your site never gets sent to Google or Facebook or wherever. That boundary matters a lot for security.
Setting a cookie in code
In Hectoday HTTP, you set a cookie by adding a Set-Cookie header to your response. That is the whole API. There is no special cookie helper function.
route.post("/login", {
resolve: async (c) => {
// ... verify credentials ...
return Response.json(
{ message: "Login successful" },
{
status: 200,
headers: { "set-cookie": "session=abc123" },
},
);
},
}); Let me flag something important here. Hectoday HTTP does not wrap the cookie API. You are working directly with the Web Standard Response API. A cookie is just a header on the response. That is on purpose: once you understand this, there is nothing extra to learn about cookies across frameworks. They are all doing the same thing under the hood.
Reading a cookie in code
On the next request, the browser will include that cookie in the request’s Cookie header. You read it like any other header:
route.get("/dashboard", {
resolve: (c) => {
const cookieHeader = c.request.headers.get("cookie");
console.log(cookieHeader);
// "session=abc123"
return Response.json({ page: "dashboard" });
},
}); One thing to know: the Cookie header is a single string. If there are multiple cookies, they are mashed together, separated by semicolons:
Cookie: session=abc123; theme=dark; lang=en Parsing cookies
Since the cookie header is just text, you have to parse it to extract individual values. The basic approach is to split by semicolons, then split each piece by the first =:
"session=abc123; theme=dark; lang=en"
→ split by ";" → ["session=abc123", "theme=dark", "lang=en"]
→ split by "=" → { session: "abc123", theme: "dark", lang: "en" } We will write an actual parseCookies helper function in the Building session management lesson when we need it for real. For now, the important takeaway is that cookies are just text. You split it apart with a couple of .split() calls. No magic.
Cookie attributes
The Set-Cookie header supports a bunch of optional attributes that control how the cookie behaves. We will dig into these in detail in the Cookie security lesson at the end of this section. But here is a preview so the syntax does not startle you when we start using them:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400 Each thing after the name=value pair controls something specific:
HttpOnly, JavaScript running on the page cannot read this cookieSecure, only send this cookie over HTTPSSameSite, controls when the cookie gets sent with cross-site requestsPath, which URL paths the cookie applies toMax-Age, how long (in seconds) the cookie lives before the browser deletes it
We are going to talk about why each of these matters for security later. For now, just know they exist and that they are written as a semicolon-separated list after the name/value pair.
Cookies vs. custom headers
You might be wondering: if cookies are just HTTP headers, why not just use a regular header? Why go through all this cookie stuff? For example, why not have the client attach Authorization: abc123 on every request?
You can absolutely do that, and we will do exactly that for JWTs in Section 4. The difference is who does the work:
- Cookies: The browser handles everything. It stores the value, attaches it to every request automatically, and respects all the attributes like expiry and domain. Your JavaScript does not have to do anything at all.
- Custom headers: Your JavaScript code is responsible for storing the value somewhere (localStorage? a variable? in memory?), attaching it to every request manually, and handling expiry yourself.
Cookies are the browser’s built-in mechanism for persistent, automatic state. That is why they are the foundation of session-based authentication. They are free in a way that manual headers never will be.
In the next lesson, we take this building block and turn it into an actual session: a way for the server to remember who somebody is across requests, using just a random string in a cookie.
When does the browser send a cookie to the server?
How do you set a cookie in a Hectoday HTTP response?