Express Static Files — Complete Guide to Serving CSS, JS, and Images
In this tutorial, you will learn about Express Static Files. We cover key concepts, practical examples, and best practices to help you master this topic.
Express serves static files like CSS, JavaScript, and images using built-in express.static middleware, allowing browsers to access assets directly without route handlers.
What You'll Learn
By the end of this tutorial, you'll configure express.static for multiple directories, set virtual path prefixes, control Caching, optimize for production, and handle file not found scenarios.
Why Static Files Matter
Every web application needs CSS, client-side JavaScript, and images. express.static handles file serving efficiently with built-in caching, ETag support, and directory traversal prevention.
Real-World Use
A corporate website serves its CSS framework from /css, JavaScript bundles from /js, and product images from /images. express.static handles all three directories behind the same Express server.
Static Files Learning Path
flowchart LR
A[Template Engines] --> B[Static Files]
B --> C[Sessions]
C --> D[Security]
D --> E[REST API]
A --> F{You Are Here}
style F fill:#f90,color:#fff
Basic Static File Serving
import express from "express";
import path from "node:path";
const app = express();
app.use(express.static("public"));
// Files in ./public are served at the root:
// public/style.css → /style.css
// public/js/app.js → /js/app.js
Multiple Static Directories
app.use(express.static("public"));
app.use(express.static("uploads"));
// If same filename exists in both, public takes precedence
Virtual Path Prefix
app.use("/static", express.static("public"));
// public/style.css → /static/style.css
app.use("/assets", express.static("uploads"));
// uploads/images/logo.png → /assets/images/logo.png
Absolute Path
app.use("/static", express.static(path.join(process.cwd(), "public")));
// Always use absolute paths for reliable asset resolution
Caching Configuration
app.use(express.static("public", {
maxAge: "1d", // Cache for 1 day
immutable: true, // Never re-validate for hashed files
etag: true, // Enable ETag headers
lastModified: true, // Enable Last-Modified headers
setHeaders: (res, path) => {
if (path.endsWith(".css")) {
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
}
}
}));
Production Best Practices
const isProduction = process.env.NODE_ENV === "production";
app.use(express.static(path.join(process.cwd(), "dist"), {
maxAge: isProduction ? "1y" : 0,
immutable: isProduction,
redirect: false,
dotfiles: "ignore"
}));
Serving Build Artifacts
// For React/Vue/etc. built apps
app.use(express.static(path.join(process.cwd(), "client/build")));
app.get("*", (req, res) => {
res.sendFile(path.join(process.cwd(), "client/build/index.html"));
});
Common Mistakes
1. Forgetting Absolute Paths in Production
Relative paths like "public" depend on the working directory. Use path.resolve or __dirname for reliability.
2. Serving Static Files Before Security Middleware
Sensitive files in the static directory are publicly accessible. Place security middleware before express.static.
3. No Cache Control in Production
Without cache headers, browsers re-download assets on every page load, wasting bandwidth and slowing page loads.
4. Serving node_modules Directly
Never serve node_modules publicly. It exposes source code, increases attack surface, and adds unnecessary requests.
5. Directory Listing Enabled
By default, express.static doesn't list directories. Don't accidentally enable it by misconfiguring options.
Practice Questions
1. What does express.static do?
It serves files from a directory on the filesystem, mapping file paths to URL paths. It handles headers, caching, and security automatically.
2. How do you serve files under a different URL path?
Use a mount path: app.use("/assets", express.static("public")). Files in public/ are served at /assets/filename.
3. How do you set cache headers for static files?
Pass maxAge option in milliseconds or string format: app.use(express.static("public", { maxAge: "7d" })).
4. Why use absolute paths for express.static?
The working directory may change. Absolute paths guarantee files are found regardless of where the Process is started.
5. Challenge: Configure express.static for a production app with versioned assets, long-term caching, and fallback to index.html for SPA routing.
app.use("/static", express.static(path.join(__dirname, "dist"), {
maxAge: "1y",
immutable: true
}));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "dist/index.html"));
});
FAQ
Mini Project: Static Asset Server
Create a static file server with versioned assets and cache invalidation.
import express from "express";
import path from "node:path";
import crypto from "node:crypto";
import fs from "node:fs";
const app = express();
const hashFile = (filePath) => {
const content = fs.readFileSync(filePath);
return crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
};
const files = fs.readdirSync("public/css");
const hashes = {};
files.forEach(f => { hashes[f] = hashFile(path.join("public/css", f)); });
app.set("view engine", "ejs");
app.get("/", (req, res) => {
res.render("index", { styleHash: hashes["style.css"] });
});
app.use(express.static("public", { maxAge: "1y", immutable: true }));
app.listen(3000);
What's Next
Express Sessions Express Security REST API Express
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro