Sessions
Cookie-based sessions with pluggable stores and flash message support.
Overview
@tekir/session provides cookie-based HTTP sessions. A session ID is stored in a signed cookie; the actual data lives in a configurable store (memory, Redis, or a SQL table). The middleware reads the cookie, hydrates a Session object, attaches it to ctx.session, and automatically persists changes after the response.
bun add @tekir/sessionConfiguration
Create config/session.ts and set the driver field. The SessionProvider creates the appropriate store automatically.
The session cookie is hardened by default: it is sent with HttpOnly, SameSite=Lax, and Secure. Secure defaults to true in production (and when NODE_ENV is unset); set cookie.secure: false to opt out, for example when serving over plain HTTP during local development.
import env from '#env'
import type { SessionConfig } from '@tekir/session'
export default {
driver: 'memory',
age: 7200, // session lifetime in seconds (default: 2h)
cookieName: 'tekir_session',
cookie: {
httpOnly: true,
secure: env.NODE_ENV === 'production',
sameSite: 'lax' as const,
path: '/'
}
} satisfies SessionConfigFor production with Redis (requires bun add @tekir/redis):
import env from '#env'
import type { SessionConfig } from '@tekir/session'
export default {
driver: 'redis',
prefix: 'sess:', // key prefix in Redis (default: 'sess:')
age: 86400, // 24 hours
cookieName: 'tekir_session',
cookie: {
httpOnly: true,
secure: env.NODE_ENV === 'production',
sameSite: 'lax' as const
}
} satisfies SessionConfig
// Requires @tekir/redis: the provider reads config('redis') for connection optionsOr with a database table:
import env from '#env'
import type { SessionConfig } from '@tekir/session'
export default {
driver: 'database',
table: 'sessions', // table name, auto-created (default: 'sessions')
age: 7200,
cookieName: 'tekir_session',
cookie: {
httpOnly: true,
secure: env.NODE_ENV === 'production',
sameSite: 'lax' as const
}
} satisfies SessionConfig
// Uses the registered 'db' service: add DatabaseProvider before SessionProviderRegister SessionProvider in your kernel:
import type { TekirApp } from '@tekir/core'
import { SessionProvider } from '@tekir/session'
export default function({ app }: TekirApp) {
app.registerAll([SessionProvider])
}Access the session in any controller or middleware via ctx.session:
import type { HttpContext } from '@tekir/core'
export async function index({ session, response }: HttpContext) {
const items = session.get<string[]>('items', [])
return response.ok({ items })
}Session Class
ctx.session is an instance of the Session class. It exposes two namespaces: the main data bag and the flash bag (one-time messages). Both are serialized to the store together on every save.
put / get / has
The three fundamental operations. All methods are synchronous; IO only happens when the session is saved after the response.
// Store a value
ctx.session.put('userId', 42)
ctx.session.put('theme', 'dark')
// Retrieve a value (with optional default)
const userId = ctx.session.get<number>('userId') // 42
const locale = ctx.session.get<string>('locale', 'en') // 'en' if not set
// Check existence
const loggedIn = ctx.session.has('userId') // trueall / pull / forget / clear
// all(), snapshot of the entire session data (shallow copy)
const data = ctx.session.all()
// { userId: 42, theme: 'dark' }
// pull(): read and remove in one call
const theme = ctx.session.pull<string>('theme', 'light')
// 'dark': and 'theme' is now removed from the session
// forget(): remove a single key
ctx.session.forget('userId')
// clear(): wipe every key
ctx.session.clear()- all(): returns a shallow copy. Mutating it does not affect the session.
- pull(key, default?): reads and removes in one step. Ideal for one-off tokens.
- forget(key): deletes a single key.
- clear(): removes every key (flash is unaffected).
increment / decrement
Convenience helpers for numeric counters. Both accept an optional step argument.
// increment(key, by?), add to a numeric counter (default by = 1)
ctx.session.put('views', 0)
ctx.session.increment('views') // 1
ctx.session.increment('views') // 2
ctx.session.increment('views', 5) // 7
// decrement(key, by?): subtract from a numeric counter (default by = 1)
ctx.session.decrement('views') // 6
ctx.session.decrement('views', 3) // 3Flash Messages
Flash data is written on the current request and available on the next request. Reading with getFlash() consumes and removes it, the standard pattern for post-redirect-get messages.
// flash(), store a value that survives only one additional request
ctx.session.flash('success', 'Profile updated successfully.')
ctx.session.flash('errors', { email: 'Already taken.' })
// In the next request:
const msg = ctx.session.getFlash<string>('success')
// 'Profile updated successfully.'
// Reading via getFlash() immediately removes the key.
// Check without consuming
const hasError = ctx.session.hasFlash('errors') // true
// Get all flash data (without consuming)
const all = ctx.session.flashAll()Regenerate & Destroy
Call regenerate() after login to swap the session ID and prevent fixation attacks. Call destroy() on logout to delete the session entirely.
// regenerate(), replace the session ID (use after login to prevent fixation)
const newId = await ctx.session.regenerate()
ctx.session.put('userId', user.id)
// destroy(): delete session data from the store and clear the in-memory state
await ctx.session.destroy()Drivers
- memory: in-process
Map. Data lost on restart, not shared across workers. Default for development. - redis: JSON in Redis with
EXPIRE. Shared across instances. Requires@tekir/redis: reads connection options fromconfig/redis.ts. - database: SQL table with
INSERT OR REPLACE. Table auto-created. RequiresDatabaseProviderregistered beforeSessionProvider.
Config Reference
All options accepted by config/session.ts:
import type { SessionConfig } from '@tekir/session'
const config: SessionConfig = {
// driver: 'memory' | 'redis' | 'database' (default: 'memory')
driver: 'memory',
// age: session lifetime in seconds (default: 7200)
age: 7200,
// cookieName: name of the session cookie (default: 'tekir_session')
cookieName: 'tekir_session',
// cookie: cookie attributes (secure defaults: HttpOnly + SameSite + Secure)
cookie: {
httpOnly: true, // default: true
secure: true, // default: true in production / when NODE_ENV is unset;
// set false to allow plain-HTTP local development
sameSite: 'lax', // 'strict' | 'lax' | 'none' (default: 'lax')
path: '/',
domain: undefined
},
// Redis driver options
prefix: 'sess:', // key prefix (default: 'sess:')
// Database driver options
table: 'sessions' // table name (default: 'sessions')
}