or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cookie-management.mdindex.mdsession-api.mdsession-config.mdsession-stores.md
tile.json

session-stores.mddocs/

Session Stores

Session store interface and implementations for persisting session data beyond memory, including the built-in MemoryStore and requirements for custom store implementations.

Capabilities

Store Base Class

Abstract base class that all session stores must extend, providing common functionality and event handling.

/**
 * Abstract base class for session stores
 * Extends EventEmitter for connection status events
 */
class Store extends EventEmitter {
  /**
   * Regenerate session by destroying old one and creating new one
   * @param req - Express request object
   * @param callback - Callback function
   */
  regenerate(req: Request, callback: (err?: Error) => void): void;
  
  /**
   * Load session by ID and create Session instance
   * @param sid - Session ID
   * @param callback - Callback with error and session
   */
  load(sid: string, callback: (err?: Error, session?: Session) => void): void;
  
  /**
   * Create Session instance from session data
   * @param req - Express request object
   * @param sess - Session data
   * @returns Session instance
   */
  createSession(req: Request, sess: any): Session;
}

Usage Examples:

const { Store } = require('express-session');
const EventEmitter = require('events');

// Custom store extending base Store class
class CustomStore extends Store {
  constructor(options = {}) {
    super();
    this.options = options;
    this.sessions = new Map();
  }
  
  // Implement required methods
  destroy(sid, callback) {
    this.sessions.delete(sid);
    if (callback) callback();
  }
  
  get(sid, callback) {
    const session = this.sessions.get(sid);
    if (callback) callback(null, session);
  }
  
  set(sid, session, callback) {
    this.sessions.set(sid, session);
    if (callback) callback();
  }
}

Required Store Methods

Methods that every session store implementation must provide.

interface RequiredStoreMethods {
  /**
   * Destroy/delete session by session ID
   * @param sid - Session ID to destroy
   * @param callback - Callback function called when complete
   */
  destroy(sid: string, callback: (err?: Error) => void): void;
  
  /**
   * Get session data by session ID
   * @param sid - Session ID to retrieve
   * @param callback - Callback with error and session data
   */
  get(sid: string, callback: (err?: Error, session?: any) => void): void;
  
  /**
   * Set/update session data by session ID
   * @param sid - Session ID to store
   * @param session - Session data to store
   * @param callback - Callback function called when complete
   */
  set(sid: string, session: any, callback: (err?: Error) => void): void;
}

Usage Examples:

// Redis store implementation example
class RedisStore extends Store {
  constructor(client) {
    super();
    this.client = client;
  }
  
  destroy(sid, callback) {
    this.client.del(`session:${sid}`, callback);
  }
  
  get(sid, callback) {
    this.client.get(`session:${sid}`, (err, data) => {
      if (err) return callback(err);
      try {
        const session = data ? JSON.parse(data) : null;
        callback(null, session);
      } catch (parseErr) {
        callback(parseErr);
      }
    });
  }
  
  set(sid, session, callback) {
    const data = JSON.stringify(session);
    const ttl = session.cookie && session.cookie.maxAge 
      ? Math.floor(session.cookie.maxAge / 1000) 
      : 86400; // 24 hours default
      
    this.client.setex(`session:${sid}`, ttl, data, callback);
  }
}

Recommended Store Methods

Methods that stores should implement for optimal functionality.

interface RecommendedStoreMethods {
  /**
   * Touch session to update expiration without changing data
   * @param sid - Session ID to touch
   * @param session - Current session data
   * @param callback - Callback function called when complete
   */
  touch?(sid: string, session: any, callback: (err?: Error) => void): void;
}

Usage Examples:

// Store with touch method implementation
class DatabaseStore extends Store {
  // ... required methods ...
  
  touch(sid, session, callback) {
    const expiry = new Date(Date.now() + (session.cookie.maxAge || 86400000));
    
    this.db.query(
      'UPDATE sessions SET expires = ? WHERE session_id = ?',
      [expiry, sid],
      callback
    );
  }
}

// Using touch functionality
app.use(session({
  store: new DatabaseStore(),
  resave: false, // Can set to false since store implements touch
  saveUninitialized: false
}));

Optional Store Methods

Additional methods that stores may implement for enhanced functionality.

interface OptionalStoreMethods {
  /**
   * Get all sessions in the store
   * @param callback - Callback with error and sessions object
   */
  all?(callback: (err?: Error, sessions?: { [sid: string]: any }) => void): void;
  
  /**
   * Clear all sessions from the store
   * @param callback - Callback function called when complete
   */
  clear?(callback: (err?: Error) => void): void;
  
  /**
   * Get the count of sessions in the store
   * @param callback - Callback with error and session count
   */
  length?(callback: (err?: Error, count?: number) => void): void;
}

Usage Examples:

// Admin endpoints using optional store methods
app.get('/admin/sessions', (req, res) => {
  if (req.sessionStore.all) {
    req.sessionStore.all((err, sessions) => {
      if (err) return res.status(500).json({ error: 'Failed to get sessions' });
      
      const sessionList = Object.keys(sessions).map(sid => ({
        id: sid,
        data: sessions[sid],
        lastAccess: sessions[sid].lastAccess
      }));
      
      res.json({ sessions: sessionList });
    });
  } else {
    res.status(501).json({ error: 'Store does not support listing sessions' });
  }
});

app.delete('/admin/sessions', (req, res) => {
  if (req.sessionStore.clear) {
    req.sessionStore.clear((err) => {
      if (err) return res.status(500).json({ error: 'Failed to clear sessions' });
      res.json({ message: 'All sessions cleared' });
    });
  } else {
    res.status(501).json({ error: 'Store does not support clearing sessions' });
  }
});

app.get('/admin/session-count', (req, res) => {
  if (req.sessionStore.length) {
    req.sessionStore.length((err, count) => {
      if (err) return res.status(500).json({ error: 'Failed to get session count' });
      res.json({ count });
    });
  } else {
    res.status(501).json({ error: 'Store does not support session counting' });
  }
});

MemoryStore Implementation

Built-in memory-based session store for development use.

/**
 * In-memory session store (development only)
 * Not suitable for production due to memory leaks and scalability issues
 */
class MemoryStore extends Store {
  constructor();
  
  /** Internal session storage object */
  sessions: { [sid: string]: string };
  
  /** Get all active sessions */
  all(callback: (err?: Error, sessions?: { [sid: string]: any }) => void): void;
  
  /** Clear all sessions */
  clear(callback: (err?: Error) => void): void;
  
  /** Destroy session by ID */
  destroy(sid: string, callback: (err?: Error) => void): void;
  
  /** Get session by ID */
  get(sid: string, callback: (err?: Error, session?: any) => void): void;
  
  /** Set session by ID */
  set(sid: string, session: any, callback: (err?: Error) => void): void;
  
  /** Get session count */
  length(callback: (err?: Error, count?: number) => void): void;
  
  /** Touch session to update expiration */
  touch(sid: string, session: any, callback: (err?: Error) => void): void;
}

Usage Examples:

const { MemoryStore } = require('express-session');

// Explicit MemoryStore usage (development only)
const store = new MemoryStore();

app.use(session({
  store: store,
  secret: 'dev-secret',
  resave: false,
  saveUninitialized: false
}));

// Development session management
if (process.env.NODE_ENV === 'development') {
  app.get('/dev/sessions', (req, res) => {
    store.all((err, sessions) => {
      res.json({ 
        count: Object.keys(sessions).length,
        sessions: sessions 
      });
    });
  });
  
  app.delete('/dev/sessions', (req, res) => {
    store.clear(() => {
      res.json({ message: 'All sessions cleared' });
    });
  });
}

Store Event Handling

Session stores emit events for connection status and error handling.

interface StoreEvents {
  /** Emitted when store becomes unavailable */
  'disconnect': () => void;
  
  /** Emitted when store becomes available */
  'connect': () => void;
  
  /** Emitted on store errors */
  'error': (error: Error) => void;
}

Usage Examples:

// Store event handling
const store = new RedisStore(redisClient);

store.on('connect', () => {
  console.log('Session store connected');
});

store.on('disconnect', () => {
  console.log('Session store disconnected');
});

store.on('error', (err) => {
  console.error('Session store error:', err);
});

// Graceful degradation
app.use(session({
  store: store,
  secret: 'secret',
  resave: false,
  saveUninitialized: false
}));

// Monitor store health
app.get('/health', (req, res) => {
  // Store readiness is tracked internally by express-session
  res.json({
    status: 'ok',
    sessionStore: 'connected' // Sessions will work normally
  });
});

Custom Store Implementation Example

Complete example of implementing a custom database store.

/**
 * Example custom database store implementation
 */
class DatabaseStore extends Store {
  constructor(database, options = {}) {
    super();
    this.db = database;
    this.options = {
      tableName: 'sessions',
      ttl: 86400, // 24 hours default TTL
      ...options
    };
  }
}

Usage Examples:

const mysql = require('mysql2');

class MySQLStore extends Store {
  constructor(connection, options = {}) {
    super();
    this.db = connection;
    this.tableName = options.tableName || 'sessions';
    
    // Create sessions table if it doesn't exist
    this.createTable();
  }
  
  createTable() {
    const sql = `
      CREATE TABLE IF NOT EXISTS ${this.tableName} (
        session_id VARCHAR(128) PRIMARY KEY,
        expires DATETIME,
        data TEXT
      )
    `;
    this.db.execute(sql);
  }
  
  destroy(sid, callback) {
    const sql = `DELETE FROM ${this.tableName} WHERE session_id = ?`;
    this.db.execute(sql, [sid], callback);
  }
  
  get(sid, callback) {
    const sql = `
      SELECT data FROM ${this.tableName} 
      WHERE session_id = ? AND expires > NOW()
    `;
    
    this.db.execute(sql, [sid], (err, results) => {
      if (err) return callback(err);
      
      try {
        const session = results.length > 0 
          ? JSON.parse(results[0].data) 
          : null;
        callback(null, session);
      } catch (parseErr) {
        callback(parseErr);
      }
    });
  }
  
  set(sid, session, callback) {
    const expires = new Date(Date.now() + (session.cookie.maxAge || 86400000));
    const data = JSON.stringify(session);
    
    const sql = `
      INSERT INTO ${this.tableName} (session_id, expires, data) 
      VALUES (?, ?, ?) 
      ON DUPLICATE KEY UPDATE expires = VALUES(expires), data = VALUES(data)
    `;
    
    this.db.execute(sql, [sid, expires, data], callback);
  }
  
  touch(sid, session, callback) {
    const expires = new Date(Date.now() + (session.cookie.maxAge || 86400000));
    const sql = `UPDATE ${this.tableName} SET expires = ? WHERE session_id = ?`;
    
    this.db.execute(sql, [expires, sid], callback);
  }
  
  clear(callback) {
    const sql = `DELETE FROM ${this.tableName}`;
    this.db.execute(sql, callback);
  }
  
  length(callback) {
    const sql = `SELECT COUNT(*) as count FROM ${this.tableName} WHERE expires > NOW()`;
    this.db.execute(sql, (err, results) => {
      if (err) return callback(err);
      callback(null, results[0].count);
    });
  }
}

// Usage
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'myapp'
});

const store = new MySQLStore(connection);

app.use(session({
  store: store,
  secret: 'my-secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 1000 * 60 * 60 * 24 // 24 hours
  }
}));

Error Handling in Stores

Best practices for error handling in session store implementations.

interface StoreErrorHandling {
  /** Handle store connection errors gracefully */
  handleConnectionError(error: Error): void;
  
  /** Handle data corruption or parsing errors */
  handleDataError(error: Error, sid: string): void;
}

Usage Examples:

class RobustStore extends Store {
  constructor(options) {
    super();
    this.options = options;
    this.setupErrorHandling();
  }
  
  setupErrorHandling() {
    this.on('error', (err) => {
      console.error('Store error:', err);
      // Emit disconnect event to disable sessions temporarily
      this.emit('disconnect');
      
      // Attempt reconnection
      setTimeout(() => this.reconnect(), 5000);
    });
  }
  
  get(sid, callback) {
    try {
      // Store operation
      this.performGet(sid, (err, session) => {
        if (err) {
          // Log error but don't crash the application
          console.error(`Session get error for ${sid}:`, err);
          
          // Treat as session not found rather than error
          return callback(null, null);
        }
        callback(null, session);
      });
    } catch (syncErr) {
      console.error('Synchronous store error:', syncErr);
      callback(null, null); // Graceful degradation
    }
  }
  
  set(sid, session, callback) {
    try {
      this.performSet(sid, session, (err) => {
        if (err) {
          console.error(`Session set error for ${sid}:`, err);
          // Continue without throwing - session will be lost but app continues
        }
        if (callback) callback();
      });
    } catch (syncErr) {
      console.error('Synchronous store error:', syncErr);
      if (callback) callback();
    }
  }
}