Session store interface and implementations for persisting session data beyond memory, including the built-in MemoryStore and requirements for custom store implementations.
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();
}
}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);
}
}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
}));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' });
}
});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' });
});
});
}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
});
});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
}
}));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();
}
}
}