Difficulty: Beginner
Web applications need to serve static files such as HTML pages, CSS stylesheets, JavaScript files, images, fonts, and downloadable documents. Express provides the built-in express.static() middleware to serve files from a directory on your file system. This is one of the most commonly used middleware functions in Express.
The express.static() function takes a directory path as its argument and returns a middleware function. When a request comes in, it checks if a file matching the request path exists in the specified directory. If it does, it sends the file with the correct Content-Type header (determined automatically from the file extension). If the file does not exist, it passes the request to the next middleware in the chain.
The conventional directory for static files is named 'public' and sits at the root of your project. Inside public/, you typically organize files into subdirectories: css/ for stylesheets, js/ for client-side JavaScript, images/ for image files, and so on. When you mount express.static('public'), a file at public/css/style.css becomes accessible at http://localhost:3000/css/style.css (notice that 'public' is not part of the URL).
You can also mount static files under a virtual path prefix by passing it as the first argument to app.use(). For example, app.use('/static', express.static('public')) would serve public/css/style.css at /static/css/style.css. This is useful when you want to namespace your static files or serve from multiple directories.
It is important to use path.join() or path.resolve() to construct the directory path, especially when your application might be started from different working directories. Using path.join(__dirname, 'public') ensures the path is always relative to the file where the code lives, not the current working directory. This prevents the common issue of static files not being found when running the app from a different directory.
const express = require('express');
const path = require('path');
const app = express();
// Serve static files from the 'public' directory
app.use(express.static(path.join(__dirname, 'public')));
// API routes still work alongside static files
app.get('/api/data', (req, res) => {
res.json({ message: 'API is working' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
console.log('Static files served from:', path.join(__dirname, 'public'));
});
// Directory structure:
// project/
// ├── public/
// │ ├── index.html
// │ ├── css/
// │ │ └── style.css
// │ ├── js/
// │ │ └── main.js
// │ └── images/
// │ └── logo.png
// └── server.js
//
// Accessible URLs:
// http://localhost:3000/ -> public/index.html
// http://localhost:3000/css/style.css -> public/css/style.css
// http://localhost:3000/images/logo.png -> public/images/logo.png
express.static() automatically maps URLs to files in the public directory. The 'public' folder name is not included in the URL. path.join(__dirname, 'public') ensures the path works regardless of where the app is started from.
const express = require('express');
const path = require('path');
const app = express();
// Serve files under /static prefix
app.use('/static', express.static(path.join(__dirname, 'public')));
// Serve uploaded files from a different directory
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
// Serve node_modules for frontend libraries
app.use('/vendor', express.static(path.join(__dirname, 'node_modules')));
app.listen(3000, () => {
console.log('Server running on port 3000');
});
// URL mapping:
// /static/css/style.css -> public/css/style.css
// /uploads/photo.jpg -> uploads/photo.jpg
// /vendor/bootstrap/dist/css/bootstrap.min.css -> node_modules/bootstrap/...
You can mount multiple static directories with different prefixes. This is useful for separating application assets, user uploads, and third-party libraries. Each mount is independent - Express checks them in order until it finds a matching file.
const express = require('express');
const path = require('path');
const app = express();
// Static files with caching and custom options
app.use(express.static(path.join(__dirname, 'public'), {
maxAge: '1d', // Cache static assets for 1 day
etag: true, // Enable ETag headers for caching
lastModified: true, // Send Last-Modified header
index: 'index.html', // Default file for directory requests
dotfiles: 'ignore', // Ignore dotfiles (.env, .gitignore)
extensions: ['html'] // Serve .html files without extension
}));
// Fallback for SPA (Single Page Application)
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
express.static accepts an options object for fine-tuning. maxAge sets browser cache duration. The extensions option allows accessing '/about' instead of '/about.html'. The SPA fallback catches all unmatched routes and serves index.html, letting the frontend router handle the URL.
express.static, Public Directory, Path Configuration, Virtual Path Prefix, Static Assets