Putnami
DocsGitHub

Licensed under FSL-1.1-MIT

Getting Started
Concepts
How To
Build A Web App
Build An Api Service
Share Code Between Projects
Configure Your App
Add Persistence
Add Authentication
Add Background Jobs
Principles
Tooling & Workspace
Workspace Overview
Cli
Jobs & Commands
SDK
Error Handling
Extensions
Typescript
Go
Python
Docker
Ci
Frameworks
Typescript
OverviewWebReact RoutingForms And ActionsStatic FilesApiErrors And ResponsesConfigurationLoggingHttp And MiddlewareDependency InjectionPlugins And LifecycleSessionsAuthPersistenceEventsStorageCachingWebsocketsTestingHealth ChecksTelemetryProto GrpcSmart Client
Go
OverviewHttpDependency InjectionPlugins And LifecycleConfigurationSecurityPersistenceErrorsEventsStorageCachingLoggingTelemetryGrpcService ClientsValidationOpenapiTesting
Platform
  1. DocsSeparator
  2. FrameworksSeparator
  3. TypescriptSeparator
  4. Sessions

Sessions

Sessions in @putnami/application provide stateful user data storage across requests with multiple backend options.

Getting started

Configuration

Configure sessions in .env.local.yaml:

session:
  store: 'cookie'
  cookieName: 'session'
  cookieSecret: 'your-32-character-secret-key!!!'
  cookieSalt: 'your-16-char-salt'
  ttl: 604800
  secure: true
  sameSite: 'lax'

Configuration options

  • store - Storage backend: 'cookie', 'memory', or 'database'
  • cookieName - Name of the session cookie (default: 'session')
  • cookieSecret - Encryption key (32+ characters, required)
  • cookieSalt - Encryption salt (16+ characters, required)
  • ttl - Session lifetime in seconds (default: 604800 = 7 days)
  • secure - HTTPS-only cookies (default: true in production)
  • sameSite - Cookie SameSite policy: 'strict', 'lax', or 'none'
  • algorithm - Encryption algorithm (default: 'aes-256-cbc')

Basic usage

Setting session data

import { endpoint, setSession } from '@putnami/application';

export default endpoint()
  .body({ userId: String })
  .handle(async (ctx) => {
    const body = await ctx.body();

    // Set session data
    await setSession({
      userId: body.userId,
      loginTime: Date.now(),
    });

    return { ok: true };
  });

Getting session data

import { endpoint, useSession } from '@putnami/application';

export default endpoint(async () => {
  const session = await useSession<{
    userId: string;
    loginTime: number;
  }>();

  if (!session) {
    return { authenticated: false };
  }

  return {
    authenticated: true,
    userId: session.userId,
    loginTime: session.loginTime,
  };
});

Deleting session data

import { endpoint, deleteSession } from '@putnami/application';

export default endpoint(async () => {
  // Clear the session (logout)
  await deleteSession();

  return { ok: true };
});

Storage backends

Cookie storage

Session data is encrypted and stored directly in the cookie. Best for small amounts of data.

session:
  store: 'cookie'
  cookieSecret: 'your-32-character-secret-key!!!'
  cookieSalt: 'your-16-char-salt'

Pros:

  • No server-side storage needed
  • Works across multiple server instances
  • Simple setup

Cons:

  • Limited to ~4KB of data
  • All data sent with every request
  • Cannot invalidate sessions server-side

Memory storage

Session data stored in server memory. Best for development or single-server deployments.

session:
  store: 'memory'
  cookieSecret: 'your-32-character-secret-key!!!'
  cookieSalt: 'your-16-char-salt'

Pros:

  • Fast access
  • No external dependencies
  • Unlimited data size

Cons:

  • Lost on server restart
  • Doesn't work with multiple server instances
  • Memory usage grows with active sessions

Database storage

Session data stored in PostgreSQL. Best for production with multiple server instances.

session:
  store: 'database'
  cookieSecret: 'your-32-character-secret-key!!!'
  cookieSalt: 'your-16-char-salt'

database:
  host: localhost
  database: myapp
  user: postgres
  password: secret

Pros:

  • Persists across restarts
  • Works with multiple server instances
  • Can invalidate sessions server-side
  • Unlimited data size

Cons:

  • Requires database setup
  • Slightly slower than memory
  • Need to clean up expired sessions

Session patterns

User authentication

// Login
export async function login(ctx: HttpRequestContext) {
  const { email, password } = await ctx.body<LoginInput>();

  const user = await validateCredentials(email, password);
  if (!user) {
    throw new UnauthorizedException('Invalid credentials');
  }

  await setSession({
    userId: user.id,
    email: user.email,
    roles: user.roles,
  });

  return { ok: true, user: { id: user.id, email: user.email } };
}

// Logout
export async function logout() {
  await deleteSession();
  return { ok: true };
}

// Get current user
export async function getCurrentUser() {
  const session = await useSession<UserSession>();

  if (!session) {
    return null;
  }

  return {
    userId: session.userId,
    email: session.email,
    roles: session.roles,
  };
}

Session middleware

import type { HttpMiddleware } from '@putnami/application';
import { useSession, unauthorized, forbidden } from '@putnami/application';

interface UserSession {
  userId: string;
  email: string;
  roles: string[];
}

export const requireSession: HttpMiddleware = async (ctx, next) => {
  const session = await useSession<UserSession>();

  if (!session) {
    return unauthorized('Session required');
  }

  return next();
};

export const requireRole = (role: string): HttpMiddleware => {
  return async (ctx, next) => {
    const session = await useSession<UserSession>();

    if (!session) {
      return unauthorized('Session required');
    }

    if (!session.roles.includes(role)) {
      return forbidden(`Role '${role}' required`);
    }

    return next();
  };
};

Flash messages

import { useSession, setSession } from '@putnami/application';

interface SessionWithFlash {
  flash?: {
    type: 'success' | 'error' | 'info';
    message: string;
  };
  // ... other session data
}

// Set a flash message
export async function setFlash(type: 'success' | 'error' | 'info', message: string) {
  const session = await useSession<SessionWithFlash>() || {};
  await setSession({
    ...session,
    flash: { type, message },
  });
}

// Get and clear flash message
export async function getFlash() {
  const session = await useSession<SessionWithFlash>();

  if (!session?.flash) {
    return null;
  }

  const flash = session.flash;

  // Clear the flash message
  await setSession({
    ...session,
    flash: undefined,
  });

  return flash;
}

Session renewal

import { useSession, setSession } from '@putnami/application';

interface UserSession {
  userId: string;
  lastActivity: number;
}

const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
const RENEWAL_THRESHOLD = 5 * 60 * 1000; // 5 minutes

export async function renewSessionIfNeeded() {
  const session = await useSession<UserSession>();

  if (!session) {
    return;
  }

  const now = Date.now();
  const timeSinceActivity = now - session.lastActivity;

  // Check if session expired
  if (timeSinceActivity > SESSION_TIMEOUT) {
    await deleteSession();
    throw new UnauthorizedException('Session expired');
  }

  // Renew if close to expiry
  if (timeSinceActivity > SESSION_TIMEOUT - RENEWAL_THRESHOLD) {
    await setSession({
      ...session,
      lastActivity: now,
    });
  }
}

Security considerations

Secret key management

Never commit session secrets. Use environment variables:

session:
  cookieSecret: ${SESSION_SECRET}
  cookieSalt: ${SESSION_SALT}

Generate strong secrets:

# Generate a 32-character secret
openssl rand -base64 32

# Generate a 16-character salt
openssl rand -base64 16

Secure cookies

Always use secure settings in production:

session:
  secure: true        # HTTPS only
  sameSite: 'lax'     # CSRF protection

Session fixation

Regenerate session ID after authentication:

export async function login(ctx: HttpRequestContext) {
  const { email, password } = await ctx.body<LoginInput>();
  const user = await validateCredentials(email, password);

  // Clear any existing session first
  await deleteSession();

  // Create new session with user data
  await setSession({
    userId: user.id,
    createdAt: Date.now(),
  });

  return { ok: true };
}

Data minimization

Only store necessary data in sessions:

// Good - minimal data
await setSession({
  userId: user.id,
});

// Bad - too much data
await setSession({
  userId: user.id,
  email: user.email,
  name: user.name,
  address: user.address,
  // ... lots of user data
});

Related guides

  • Auth
  • Configuration

On this page

  • Sessions
  • Getting started
  • Configuration
  • Configuration options
  • Basic usage
  • Setting session data
  • Getting session data
  • Deleting session data
  • Storage backends
  • Cookie storage
  • Memory storage
  • Database storage
  • Session patterns
  • User authentication
  • Session middleware
  • Flash messages
  • Session renewal
  • Security considerations
  • Secret key management
  • Secure cookies
  • Session fixation
  • Data minimization
  • Related guides