Skip to main content

HTTP server: Sessions

A session keeps a user logged in across requests. The browser stores only a random session id in a cookie, and the server maps that id to the actual session data. This example implements login, a protected endpoint, and logout, with sessions that expire after a fixed lifetime.

interface Session {
  username: string;
  expires: number;
}
Session data lives on the server. The cookie holds nothing but the id, so clients cannot read or forge the data itself.
const sessions = new Map<string, Session>();
const TTL_MS = 60 * 60 * 1000;

function getSessionId(req: Request): string | null {
  const cookie = req.headers.get("cookie") ?? "";
  const match = cookie.match(/(?:^|;\s*)session=([0-9a-f-]+)/);
  return match ? match[1] : null;
}

function getSession(req: Request): Session | null {
  const id = getSessionId(req);
  if (id === null) return null;
  const session = sessions.get(id);
  if (session === undefined) return null;
Expired sessions are pruned lazily, on the first access after their deadline. No background timer is needed.
  if (session.expires < Date.now()) {
    sessions.delete(id);
    return null;
  }
  return session;
}

Deno.serve(async (req) => {
  const { pathname } = new URL(req.url);

  if (req.method === "POST" && pathname === "/login") {
A real login checks credentials first. Here any username is accepted.
    const { username } = await req.json();
    const id = crypto.randomUUID();
    sessions.set(id, { username, expires: Date.now() + TTL_MS });
httpOnly hides the cookie from client-side JavaScript, which blocks session theft through XSS. Add Secure when serving over HTTPS.
    return new Response("logged in\n", {
      headers: {
        "set-cookie":
          `session=${id}; HttpOnly; Path=/; SameSite=Lax; Max-Age=3600`,
      },
    });
  }

  if (pathname === "/me") {
    const session = getSession(req);
    if (session === null) {
      return new Response("not logged in\n", { status: 401 });
    }
    return new Response(`hello, ${session.username}\n`);
  }

  if (req.method === "POST" && pathname === "/logout") {
Destroy the server-side state and clear the cookie. Deleting the Map entry is what actually ends the session.
    const id = getSessionId(req);
    if (id !== null) sessions.delete(id);
    return new Response("logged out\n", {
      headers: { "set-cookie": "session=; Path=/; Max-Age=0" },
    });
  }

  return new Response("not found\n", { status: 404 });
});
An in-memory Map only works while a single process serves all traffic. For multiple instances or restarts, keep the same cookie scheme but store sessions in a shared store such as Deno KV or a database.
The full flow with curl, using a cookie jar (-c saves cookies, -b sends them back): curl -s -c /tmp/jar -d '{"username":"ada"}' http://localhost:8000/login logged in curl -s -b /tmp/jar http://localhost:8000/me hello, ada curl -s -b /tmp/jar -X POST http://localhost:8000/logout logged out curl -s -b /tmp/jar http://localhost:8000/me not logged in

Run this example locally using the Deno CLI:

deno run -N https://docs.deno.com/examples/scripts/http_server_sessions.ts

Did you find what you needed?

Privacy policy