Static files, images, stylesheets, client-side JavaScript bundles, don't need a custom route handler, Express can serve an entire folder of them with one line. This lesson covers express.static() and its options.
Serving a Public Folder
express.static(root) is built-in middleware that serves files directly from a given directory. Any request matching a file inside that directory is served automatically, with correct Content-Type headers, no route definitions needed.
A common convention is a public/ folder at the project root containing images, CSS, and client-side JS.
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
// public/style.css is now available at /style.css
// public/images/logo.png is now available at /images/logo.png
path.join(__dirname, 'public') builds an absolute path, safer than a relative string that depends on the current working directory.
Mounting Static Files at a Prefix
app.use('/static', express.static(path.join(__dirname, 'public')));
// public/style.css is now available at /static/style.css
Mounting at a prefix keeps static asset URLs visually distinct from your API routes.
You can register express.static() multiple times, for multiple directories or prefixes.
express.static() automatically serves index.html for a directory request by default.
Files not found simply fall through to the next middleware/route, they don't automatically 404 the whole app.
Static assets rarely change once deployed (especially when filenames include content hashes from a build tool), so setting cache headers lets browsers avoid re-downloading them on every visit.
A common pattern for full-stack apps serves a built single-page app (React, Vue, etc.) directly from Express, falling back to index.html for client-side routing on any unmatched path.
This catch-all should be registered after your API routes so it doesn't intercept them.
Common Mistakes
Using a relative path for the static directory instead of path.join(__dirname, ...), breaking depending on where the process is started from.
Registering the SPA catch-all route before your API routes, silently swallowing every API request.
Serving sensitive files (like .env or server source) from a directory accidentally exposed as static.
Not setting any cache headers, forcing browsers to re-download unchanged assets on every visit.
Key Takeaways
express.static(root) serves an entire directory of files with one line, no manual routes needed.
Always use an absolute path built with path.join(__dirname, ...).
Cache headers (maxAge, etag) reduce repeat downloads for unchanged assets.
Serving a frontend build's index.html as a catch-all must come after your API routes.
Pro Tip
Never point express.static() at your project root or any directory containing source code, .env, or configuration files, only ever point it at a dedicated, purpose-built public assets folder.
You now know how to serve static files. Next, learn how to render dynamic HTML in the Template Engines lesson.