Skip to content

Serving Static Files With Express

Earlier in this course you built static file serving by hand, including guarding against path traversal. This lesson shows how Express's built-in express.static() middleware handles all of that automatically.

Using express.static()

express.static(root) is built-in middleware that serves files directly from a directory, setting correct Content-Type headers, handling path traversal safely, and supporting conditional requests (ETag, Last-Modified) out of the box, all the concerns you handled manually in the earlier Static Files lesson.

Mounting it with app.use(express.static('public')) means any file inside the public folder becomes accessible at the matching URL path automatically, with no route needed for each individual file.

import express from 'express';
import path from 'node:path';

const app = express();

app.use(express.static(path.join(import.meta.dirname, 'public')));

app.listen(3000);

// public/style.css is now served at /style.css
// public/images/logo.png is now served at /images/logo.png

There's no explicit route for /style.css, express.static() handles matching requests to files inside the given directory automatically.

express.static() Options

app.use(express.static('public'));
app.use('/assets', express.static('public'));
app.use(express.static('public', { maxAge: '1d' }));
  • The first argument is the folder to serve files from, relative to where the process is started.
  • A path prefix ('/assets') scopes served files under that URL segment.
  • The maxAge option sets a Cache-Control header for browser caching.
  • Multiple calls to express.static() can serve from multiple directories, checked in order.

Express Static Files Cheatsheet

Common express.static() configurations.

Goal Code
Serve from root app.use(express.static('public'))
Serve under a prefix app.use('/assets', express.static('public'))
Set cache duration express.static('public', { maxAge: '7d' })
Serve from multiple folders Call express.static() more than once
Disable directory listing Default behavior, not enabled unless configured
Custom index file express.static('public', { index: 'home.html' })

Why express.static() Is Safer Than a Hand-Rolled Version

The manual static file server from earlier in this course needed explicit checks to prevent path traversal (../../etc/passwd)-style attacks. express.static() already validates and normalizes paths internally, so requests can't escape the configured root directory, without you writing that logic yourself.

Combining Static Files With API Routes

A common pattern is serving a built frontend (React, Vue, or plain HTML/CSS/JS) as static files while exposing a JSON API from the same Express app, letting one deployable serve both.

app.use(express.static('public'));  // serves the frontend build

app.use('/api', apiRouter);          // JSON API routes

app.get('*', (req, res) => {
  res.sendFile(path.join(publicDir, 'index.html')); // SPA fallback
});

The catch-all route at the end lets client-side routing (in a single-page app) work correctly on full page reloads for non-API paths.

Common Mistakes

  • Registering express.static() after API routes that should take priority, causing unexpected file matches.
  • Forgetting a path prefix when serving assets meant to live under /assets or /static.
  • Not setting any cache duration for versioned/fingerprinted build assets, missing an easy performance win.
  • Serving an entire project directory instead of a dedicated public folder, accidentally exposing source files.

Key Takeaways

  • express.static() serves files directly from a folder with correct headers and built-in path safety.
  • A path prefix argument scopes served files under a specific URL segment.
  • Multiple express.static() calls can serve from multiple directories.
  • A catch-all route after static and API routes enables single-page app client-side routing.

Pro Tip

Keep only files meant to be public inside your static folder (commonly named public), never point express.static() at your project root, it would expose source code, .env files, and configuration to anyone who requests them by path.