Primate LogoPrimate

Sessions

Sessions let you persist state across requests: authentication, user preferences, shopping carts, or any other server-side data you want tied to a client. Primate manages session cookies, validates session data, and ensures changes are committed only if your route succeeds.

Configuration

Sessions are configured in config/session.ts. By default Primate uses an in-memory store, but you may also use any store defined in stores or define one directly in config/session.ts.

Session options

Option Default Description
cookie.httpOnly true mark cookie as HttpOnly
cookie.maxAge none persist cookie for this many seconds
cookie.name "session_id" name of the session cookie
cookie.path "/" path for which the cookie is valid
cookie.sameSite "Lax" SameSite cookie policy
cookie.secure app TLS setting mark cookie as Secure
store default in-memory store store managing sessions

Whether the session cookie should be marked HttpOnly (hidden from client-side JavaScript).

The number of seconds for which the browser should retain the session cookie. When omitted, the cookie is a browser session cookie and is removed when the browser session ends. Set a non-negative integer to emit Max-Age and create a persistent cookie:

export default session({
  cookie: {
    maxAge: 60 * 60 * 24 * 30,
  },
  store: Session,
});

This controls the browser cookie lifetime; ensure the session records in your store have an appropriate retention policy as well.

The name of the session cookie.

The path for which the cookie is valid.

The SameSite cookie policy: "Strict", "Lax", or "None".

Whether the session cookie should be marked Secure. By default, this follows Primate's TLS configuration. Set it explicitly when TLS terminates at a reverse proxy and Primate receives plain HTTP from that trusted proxy:

export default session({
  cookie: {
    secure: true,
  },
  store: Session,
});

The same setting is used when clearing a destroyed session cookie.

store

The session store. By default Primate uses a store managed in memory, but you can use any store defined in stores or define one directly in config/session.ts. The store will be used for both validating the data and persisting it to a database of your choice.

The store must include a session_id field of type UUID. Primate uses this to associate each stored record with the session cookie.

Example

First create the store inside stores.

import db from "@/config/db";
import p from "pema";
import store from "primate/store";

export default store({
  table: "session",
  db,
  schema: {
    id: store.key.primary(p.u32),
    session_id: p.uuid,
    user_id: p.number,
    last_active: p.date,
    // additional fields as needed
  },
});

Then refer to it in your configuration file.

import session from "primate/session";
import Session from "@/stores/Session";

export default session({
  store: Session,
});

Session facade

The session facade is the API used in routes to interact with session state. It hides cookie handling and persistence details, exposing a simple interface to create, read, update, and destroy sessions.

method / property description
id current session ID, if one exists
exists whether a session is active
create(initial) start a session with initial data; generates a new ID
get() return current session data; throws if none
try() return data if a session exists, otherwise undefined
set(data) replace session data or derive from the previous state
destroy() end the session and clear the cookie

Usage in routes

Import the session facade via @/config/session. It is bound to the session store you used in config/session.ts.

import session from "@/config/session";
import route from "primate/route";

export default route({
  get() {
    if (!session.exists) {
      session.create({ user_id: 42 });
    }

    const data = session.get();
    return `User ${data.user_id} last active at ${data.last_active.toISOString()}`;
  },
});

SessionFacade reference

interface SessionFacade<T> {
  readonly id: string | undefined;
  readonly exists: boolean;

  create(initial?: T): void;
  get(): Readonly<T>;
  try(): Readonly<T> | undefined;

  set(next: ((previous: Readonly<T>) => T) | T): void;

  destroy(): void;
}

Validation

Primate validates data passed to create and set using the provided session store.

import db from "@/config/db";
import p from "pema";
import store from "primate/store";

export default store({
  table: "session",
  db,
  schema: {
    id: store.key.primary(p.u32),
    session_id: p.uuid,
    token: p.string.min(10),
  },
});
import session from "@/config/session";
import route from "primate/route";

export default route({
  post() {
      // throws if token is shorter than 10 characters
      session.set({ token: "abc" });
  },
});

Other operations like get, try or destroy don't take input and do not trigger validation.

This ensures your session store never contains malformed data.

Previous
Validation
Next
Stores