Session Stores for Production

Omit the store option and express-session 6,354 uses its built-in MemoryStore, which warns on startup for two good reasons: it never evicts expired sessions, so it leaks, and it lives inside one process, so every restart logs everyone out and a second worker (Clustering) sees none of the first worker's sessions. A store is a tiny interface — get, set, destroy, optionally touch — so there are dozens of them. Redis 2,763 is the usual answer.

Sessions in Redis with connect-redisJavaScript
import { createClient } from 'redis';          // npm i redis connect-redis@10.0.0
import { RedisStore } from 'connect-redis';
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
app.use(session({
  store: new RedisStore({ client, prefix: 'sess:', ttl: 1800 }),
  name: '__Host-sid',
  secret: [process.env.SESSION_SECRET, process.env.SESSION_SECRET_OLD].filter(Boolean),
  resave: false, saveUninitialized: false, rolling: true,
  cookie: { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 1800000 }
}));

Redis EXPIRE does the cleanup, so ttl should match the cookie's maxAge; with rolling: true the store's touch pushes the TTL out on each request. An array as secret rotates signing keys without logging anyone out: new cookies use the first entry, old ones still verify against the second.

Session stores that implement the express-session interface
Store Package Fits Watch out for
Redis connect-redis 10.0.0 2,823 most APIs another service to run
MongoDB 1,815 connect-mongo 6.0.0 1,968 you already run Mongo TTL index does the reaping
Files session-file-store 1.5.0 one small VM no sharing between hosts
Memory + LRU memorystore 1.6.8 dev, single process lost on restart

Configure two timeouts, not one. maxAge with rolling: true is an idle timeout; an absolute one needs your own check, because a session touched every minute never expires. Record req.session.createdAt at login and destroy the session once its age passes policy.