Express Cheatsheet

Serving Static Files

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

express.static()

Serves files from a directory. Built into Express — no separate install.

const path = require('path');

// Serve files from ./public
app.use(express.static('public'));
// → GET /logo.png serves public/logo.png

// Best practice: absolute path (immune to cwd changes)
app.use(express.static(path.join(__dirname, 'public')));

Mount Path Prefix

// Serve files under /static URL prefix
app.use('/static', express.static(path.join(__dirname, 'public')));
// → GET /static/logo.png serves public/logo.png
// → GET /logo.png → 404

// Multiple static directories (searched in order)
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'uploads')));

Options Reference

app.use(express.static(path.join(__dirname, 'public'), {
  dotfiles:     'ignore',        // 'allow' | 'deny' | 'ignore' (default 'ignore')
  etag:         true,            // enable ETag generation (default true)
  extensions:   ['html', 'htm'], // try these extensions if no extension given
  fallthrough:  true,            // pass to next() on 404 (default true); false = 404 error
  immutable:    false,           // add immutable directive to Cache-Control (use with maxAge)
  index:        'index.html',    // directory index file; false to disable
  lastModified: true,            // set Last-Modified header (default true)
  maxAge:       0,               // Cache-Control max-age in ms or string ('1d', '2h')
  redirect:     true,            // redirect /dir to /dir/ for directories (default true)
  setHeaders:   null,            // function to set custom headers
}));

setHeaders Example

app.use(express.static('public', {
  setHeaders: (res, filePath, stat) => {
    if (filePath.endsWith('.html')) {
      res.set('Cache-Control', 'no-cache');
    } else {
      res.set('Cache-Control', 'public, max-age=31536000, immutable');
    }
    res.set('X-Content-Type-Options', 'nosniff');
  },
}));

Caching Strategies

// Development: no caching
app.use(express.static('public', { maxAge: 0 }));

// Production: long-lived assets with content hashing
// (filename includes hash: app.a1b2c3.js)
app.use('/assets', express.static('dist/assets', {
  maxAge: '1y',
  immutable: true,
}));

// HTML files: always revalidate (users get fresh HTML)
// Static assets: cache forever
app.use(express.static('public', {
  setHeaders: (res, path) => {
    if (path.endsWith('.html')) {
      res.set('Cache-Control', 'no-cache');
    } else {
      res.set('Cache-Control', 'public, max-age=31536000, immutable');
    }
  },
}));

Extension Fallback

// Try /about → /about.html if /about not found
app.use(express.static('public', { extensions: ['html'] }));

Index Files and SPA Fallback

// Default: serves index.html for directory requests
app.use(express.static('public', { index: 'index.html' }));

// SPA catch-all: serve index.html for all unmatched routes
// (put AFTER API routes, with fallthrough: false or manual fallback)
app.use(express.static(path.join(__dirname, 'dist')));
app.get('/{*splat}', (req, res) => {   // Express 4: app.get('*', ...)
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

Serving Multiple Directories

Files in the first directory take precedence over later ones.

app.use(express.static(path.join(__dirname, 'public')));       // checked first
app.use(express.static(path.join(__dirname, 'node_modules/bootstrap/dist'))); // fallback

Dotfiles

// Block access to .env, .git, .htaccess etc.
app.use(express.static('public', { dotfiles: 'deny' }));
// → GET /.env → 403 Forbidden

// Silently pass to next middleware (default)
app.use(express.static('public', { dotfiles: 'ignore' }));
// → GET /.env → passes to next() (likely 404)

Custom Static Middleware

For more control, stream files manually with res.sendFile():

const path = require('path');
const fs   = require('fs');

app.get('/downloads/:filename', authenticate, (req, res, next) => {
  const safeName = path.basename(req.params.filename); // prevent path traversal
  const filePath = path.join(__dirname, 'private', safeName);

  if (!fs.existsSync(filePath)) return res.status(404).send('Not found');

  res.sendFile(filePath, {
    headers: { 'Content-Disposition': `attachment; filename="${safeName}"` },
  }, (err) => {
    if (err) next(err);
  });
});

Security Considerations

  • Always use absolute paths (path.join(__dirname, ...)) — relative paths resolve from process.cwd(), which may differ when the app is launched from a different directory.
  • Set dotfiles: 'deny' in production to prevent leaking .env, .git, etc.
  • Never serve your root directory — scope to a dedicated public/ or dist/ folder.
  • Path traversal: express.static is safe against ../../ by default; custom res.sendFile calls must sanitize with path.basename() or resolve and verify the path stays within the intended directory.
// Safe path validation for custom file serving
const root = path.resolve(__dirname, 'files');
const requested = path.resolve(root, req.params.file);
if (!requested.startsWith(root + path.sep)) {
  return res.status(403).send('Forbidden');
}
res.sendFile(requested);