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.
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.
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.
You now know how Express serves static files safely. Next, learn how to design a full REST API on top of everything covered so far.