Express Cheatsheet

Cookies and Sessions

Use this Express reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Cookies

Reading Cookies — cookie-parser

npm install cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());                         // unsigned
app.use(cookieParser('my-signing-secret'));      // enables signed cookies

// In a handler:
req.cookies.name;           // plain cookie
req.signedCookies.session;  // verified signed cookie (false if tampered)

Setting Cookies — res.cookie()

// Simple
res.cookie('theme', 'dark');

// Secure, httpOnly session token
res.cookie('token', jwt, {
  httpOnly:  true,                    // not accessible from JS (prevents XSS theft)
  secure:    true,                    // HTTPS only
  sameSite:  'strict',               // 'strict' | 'lax' | 'none'
  maxAge:    7 * 24 * 60 * 60 * 1000, // ms; sets Expires too
  path:      '/',
  domain:    '.example.com',          // include subdomains
});

// Signed cookie (tamper-evident)
res.cookie('userId', '42', { signed: true });
// req.signedCookies.userId === '42' if not modified

Clearing Cookies — res.clearCookie()

res.clearCookie('token');
// Must pass same path/domain as when set, or browser won't clear it
res.clearCookie('token', { path: '/', domain: '.example.com' });

Cookie Option Reference

OptionTypeDefaultDescription
httpOnlybooleanfalseBlock JS access
securebooleanfalseHTTPS only
sameSitestringundefinedCSRF protection: 'strict', 'lax', 'none'
maxAgenumberExpiry in milliseconds
expiresDateAbsolute expiry date
pathstring'/'Cookie path scope
domainstringCookie domain scope
signedbooleanfalseHMAC-sign the value
encodefunctionencodeURIComponentValue encoder

SameSite Values

ValueBehavior
'strict'Cookie only sent on same-site requests — no cross-site at all
'lax'Sent on top-level navigation GET; not on cross-site sub-requests
'none'Always sent — requires secure: true

Sessions

Sessions store state server-side; only a session ID is in the cookie.

express-session

npm install express-session
const session = require('express-session');

app.use(session({
  secret:            process.env.SESSION_SECRET, // used to sign the session ID cookie
  resave:            false,     // don't save session if unmodified
  saveUninitialized: false,     // don't create session for unauthenticated requests
  name:              'sid',     // cookie name (default: 'connect.sid')
  cookie: {
    httpOnly:  true,
    secure:    process.env.NODE_ENV === 'production',
    sameSite:  'lax',
    maxAge:    24 * 60 * 60 * 1000, // 1 day
  },
}));

Using the Session

// Write to session
app.post('/login', (req, res) => {
  // validate credentials ...
  req.session.userId = user.id;
  req.session.role   = user.role;
  res.json({ ok: true });
});

// Read from session
app.get('/profile', (req, res) => {
  if (!req.session.userId) return res.status(401).json({ error: 'Not logged in' });
  res.json({ userId: req.session.userId });
});

// Destroy session on logout
app.post('/logout', (req, res, next) => {
  req.session.destroy((err) => {
    if (err) return next(err);
    res.clearCookie('sid');
    res.json({ ok: true });
  });
});

// Regenerate session ID (prevent session fixation after login)
app.post('/login', async (req, res, next) => {
  // ...validate...
  req.session.regenerate((err) => {
    if (err) return next(err);
    req.session.userId = user.id;
    res.json({ ok: true });
  });
});

Session Methods and Properties

MemberDescription
req.sessionSession data object (add any properties)
req.session.idSession ID (same as req.sessionID)
req.sessionIDSession ID string
req.session.cookieCookie options for this session
req.session.save(cb)Force save to store
req.session.reload(cb)Reload from store
req.session.destroy(cb)Destroy session + remove from store
req.session.regenerate(cb)Create new session ID (same data)
req.session.touch()Reset expiry

Session Stores

The default in-memory store leaks memory and loses data on restart. Use a persistent store in production.

// connect-pg-simple (PostgreSQL)
const pgSession = require('connect-pg-simple')(session);
app.use(session({
  store: new pgSession({ pool: pgPool, tableName: 'user_sessions' }),
  // ...
}));

// connect-redis (v8+, 2024 — named export; v7 was require('connect-redis').default)
const { RedisStore } = require('connect-redis');
const { createClient } = require('redis');
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
app.use(session({
  store: new RedisStore({ client }),
  // ...
}));

// connect-mongo
const MongoStore = require('connect-mongo');
app.use(session({
  store: MongoStore.create({ mongoUrl: process.env.MONGO_URL }),
  // ...
}));

JWT Cookies (Stateless Sessions)

A common alternative: store a signed JWT in an httpOnly cookie instead of a session store.

const jwt = require('jsonwebtoken');
const KEY  = process.env.JWT_SECRET;

// Login — issue JWT
app.post('/login', async (req, res) => {
  const user = await validateCredentials(req.body);
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  const token = jwt.sign({ sub: user.id, role: user.role }, KEY, { expiresIn: '7d' });
  res.cookie('token', token, {
    httpOnly: true,
    secure:   process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge:   7 * 24 * 60 * 60 * 1000,
  });
  res.json({ ok: true });
});

// Auth middleware
const authenticate = (req, res, next) => {
  const token = req.cookies.token
    || req.headers.authorization?.replace('Bearer ', '');

  if (!token) return res.status(401).json({ error: 'Unauthorized' });
  try {
    req.user = jwt.verify(token, KEY);
    next();
  } catch {
    res.clearCookie('token');
    res.status(401).json({ error: 'Invalid or expired token' });
  }
};

// Logout
app.post('/logout', (req, res) => {
  res.clearCookie('token', { path: '/' });
  res.json({ ok: true });
});

Flash Messages

Short-lived messages (e.g., "Login failed") stored in the session for one request.

npm install connect-flash
const flash = require('connect-flash');
app.use(flash());

// Set a flash message
app.post('/login', (req, res) => {
  req.flash('error', 'Invalid credentials');
  res.redirect('/login');
});

// Read flash messages (consumed on read — gone after)
app.get('/login', (req, res) => {
  res.render('login', { errors: req.flash('error') });
});

CSRF Protection

For form-based apps that use cookies, protect against CSRF. The historical csurf package is deprecated and unmaintained — implement the synchronizer-token or double-submit-cookie pattern yourself (or use a maintained package like csrf-csrf).

// Synchronizer token pattern (with express-session)
app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = require('crypto').randomBytes(32).toString('hex');
  }
  res.locals.csrfToken = req.session.csrfToken;
  next();
});

app.use((req, res, next) => {
  if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
  const token = req.body._csrf || req.headers['x-csrf-token'];
  if (token !== req.session.csrfToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  next();
});

With sameSite: 'strict' or 'lax' cookies, most CSRF attacks are already blocked without a token — but a token provides defense in depth.