Skip to content

Session storage

User authentication sessions, API tokens, and short-term cache results need to be read quickly on every request, but do not need to persist indefinitely.

Layeron KV Storage (storage.kv) is designed exactly for this. Backed by Cloudflare KV, it offers near-zero read latency from global edge locations and supports Time-to-Live (TTL) parameters to automatically clean up expired sessions.

This example demonstrates setting up a low-latency session KV namespace, logging in users (creating sessions with standard or “Remember Me” TTLs), verifying sessions, and logging out (deleting sessions).

Terminal window
import { backend } from "@layeron/core"
import { storage } from "@layeron/modules"
const app = backend()
// 1. Declare the low-latency session KV store
const sessionsStore = storage.kv({
name: "user-sessions",
namespace: "auth",
ttlSeconds: 86400, // Standard sessions expire after 24 hours (86,400 seconds)
})
app.use(sessionsStore)
// 2. Route: Create Session on Login
app.post("/api/auth/login", async (request) => {
const body = await request.json() as { email: string; rememberMe?: boolean }
const sessionId = crypto.randomUUID()
const sessionData = {
userId: "usr_100",
email: body.email,
roles: ["admin"],
createdAt: new Date().toISOString(),
}
// Determine TTL (Remember Me sessions live for 30 days)
const ttlOverride = body.rememberMe
? 30 * 24 * 60 * 60 // 30 days in seconds (2,592,000s)
: undefined; // Falls back to default 24 hours configured in constructor
// Save session details to KV
await sessionsStore.put(`session:${sessionId}`, JSON.stringify(sessionData), {
contentType: "application/json",
...(ttlOverride ? { ttlSeconds: ttlOverride } : {}), // Apply per-key TTL override
})
// Return the session token to be stored as a cookie
return Response.json({
status: "logged_in",
token: sessionId,
})
})
// 3. Route: Validate Session on API request
app.get("/api/profile", async (request) => {
const authHeader = request.headers.get("authorization")
if (!authHeader?.startsWith("Bearer ")) {
return new Response("Unauthorized", { status: 401 })
}
const token = authHeader.split(" ")[1]
const sessionHandle = await sessionsStore.get(`session:${token}`)
// If the session does not exist or has expired, get() returns null automatically
if (!sessionHandle) {
return new Response("Session expired or invalid", { status: 401 })
}
// Parse JSON session payload directly from the read handle
const userSession = await sessionHandle.json<{ userId: string; email: string; roles: string[] }>()
return Response.json({
message: `Hello, ${userSession.email}!`,
roles: userSession.roles,
})
})
// 4. Route: Logout (Delete Session)
app.post("/api/auth/logout", async (request) => {
const authHeader = request.headers.get("authorization")
const token = authHeader?.split(" ")[1]
if (token) {
// Explicitly delete the session key from KV storage
await sessionsStore.delete(`session:${token}`)
}
return Response.json({ loggedOut: true })
})
  1. Edge-Cached Latency: Verifying sessions is a read-heavy operation. Cloudflare KV automatically caches keys close to the user at the nearest edge routing node, meaning session verification takes less than 1-2 milliseconds for active users.
  2. Global Expiration Defaults: When no ttlSeconds option is supplied during a .put() write, the Storage Product Worker defaults to the 86400 seconds (24 hours) lifetime declared in the KV constructor.
  3. Per-Key Override Flexibility: By passing a custom { ttlSeconds: 2592000 } on .put(), you override the default for that specific key. This is perfect for supporting both short-lived guest sessions and long-lived “Remember Me” persistent tokens.
  4. Garbage Collection: Expired sessions are deleted by Cloudflare KV asynchronously. There are no cleanup scripts to schedule or database indexes to prune.