SDKs
Node test sessions
On this page
What a test session is
A test session, created in the dashboard (Scenarios → Shared test session), pins a configuration snapshot and a synthetic context to a token (nf_ts_…). A service that receives the token in the x-nanoflags-session header evaluates against that snapshot, with the context { ...session.context, ...yourContext }, for that request only. Sampled events from a session carry its id, and the session page lists the services that reported for it.
client.session()
const s = await flags.session(req.headers["x-nanoflags-session"], { service: "checkout-api" });
s.boolean("checkout-v2", false, { key: user.id }); // session bundle while active
s.active; // false when the session is unknown, expired, revoked or not permitted for this service
s.id; // session id, or null when not resolved
s.expiresAt; // ISO timestamp, or null
s.revision; // revision evaluations currently run against- The SDK asks the edge once per token and service (
GET /v1/session) and caches the answer until the session expires. Unknown tokens are remembered for 15 s, failed lookups for 3 s. - It never throws and never rejects. Without an active session the evaluator behaves exactly like the client, so one code path serves normal traffic and tests. When a session expires mid-flight,
activeturns false and evaluations continue on the published configuration. serviceis sent asX-NanoFlags-Service(default: the client'sservice); a session may be limited to certain services.
Express / Connect middleware
import express from "express";
import { createClient } from "@nanoflags/sdk";
import { sessionMiddleware, sessionHeaders } from "@nanoflags/sdk/node";
const flags = createClient({ sdkKey: process.env.NANOFLAGS_SDK_KEY!, baseUrl: "https://edge.nanoflags.io", service: "checkout-api" });
const app = express();
app.use(sessionMiddleware(flags)); // reads x-nanoflags-session, resolves it once per request
app.get("/cart", async (req, res) => {
const v2 = req.flags.boolean("checkout-v2", false, { key: req.user.id }); // session-scoped when active
const prices = await fetch(PRICING_URL, { headers: { ...sessionHeaders() } }); // next service joins the session
res.json({ v2, prices: await prices.json() });
});The middleware sets req.flags. Code that does not have req can call flagsFor(flags), which returns the same evaluator for the current request.
Fetch-style handlers
For Node fetch servers, Hono, or Next.js route handlers on the Node runtime:
import { withSessionHandler, flagsFor } from "@nanoflags/sdk/node";
export default {
fetch: withSessionHandler(flags, async (request: Request) => {
const v2 = flagsFor(flags).boolean("checkout-v2", false, { key: "u1" });
return Response.json({ v2 });
}),
};Queues, jobs and tests
import { withSession, sessionFlags, currentSession } from "@nanoflags/sdk/node";
await withSession(job.data.sessionToken, async () => {
const f = await sessionFlags(flags); // async variant of flagsFor for code outside the middleware
f.boolean("checkout-v2", false, { key: job.data.userId });
currentSession(); // the token, or undefined
});Header propagation
The current session lives in AsyncLocalStorage: it follows one request through awaits, timers and callbacks and is invisible to every other request. Nothing is stored on the client or in module state. Add sessionHeaders() to every outgoing call that should join the session; it returns {} outside a session, so it is always safe to spread. In the browser use client.sessionHeaders() after client.useSession(token).
On a server, client.useSession() is refused, because one client serves many requests.
API reference
| Export | Description |
|---|---|
sessionMiddleware(client, options?) | Express/Connect middleware. Sets req.flags and runs the rest of the request inside the session. |
withSessionHandler(client, handler, options?) | Wraps a fetch-style handler; the session is resolved before the handler starts. |
withSession(token, fn) | Runs fn with token as the current session. An absent or malformed token runs fn outside any session. |
flagsFor(client) | Synchronous evaluator for the current request (after the middleware or handler wrapper resolved it); otherwise behaves like the client. |
sessionFlags(client, options?) | Async variant: resolves the session once per request and client. Never rejects. |
currentSession() | Token of the current request's session, or undefined. |
sessionHeaders() | { "x-nanoflags-session": token } inside a session, else {}. |
SESSION_HEADER | "x-nanoflags-session" |
Limits and guarantees
- Client keys only ever receive client-exposed flags, in sessions too.
- Session bundles are not signed; verification does not apply to them.
- A revoked session keeps working in a process that already cached it, until its expiry time.
- Shared test sessions require the Scale plan; see Plans and limits.