Express ships with three built-in middleware functions that cover the most common request processing needs: express.json() for parsing JSON bodies, express.urlencoded() for parsing HTML form submissions, and express.static() for serving files from a directory. You have already used express.json() in every chapter so far — in this lesson you will understand exactly what each one does, how to configure it correctly, and when each is needed in a MERN application.
The Three Built-in Middleware Functions
| Middleware | Parses | Populates | Content-Type required |
|---|---|---|---|
express.json() |
JSON strings in the request body | req.body as a JavaScript object |
application/json |
express.urlencoded() |
URL-encoded form data | req.body as a JavaScript object |
application/x-www-form-urlencoded |
express.static() |
N/A — serves files | N/A — sends file response directly | N/A — reads files from disk |
body-parser package. That package was merged into Express itself in v4.16 as express.json() and express.urlencoded(). You will still see body-parser in older tutorials and codebases — it is not wrong, just unnecessary in modern Express.express.json(). Axios sends JSON with Content-Type: application/json by default. You only need express.urlencoded() if your API also needs to accept HTML form submissions — which is rare in SPAs but common when Express also serves a server-rendered HTML form.express.json() accepts request bodies up to 100kb. Large file uploads or bulk data imports can exceed this limit, causing Express to reject the request with a 413 Payload Too Large error. Increase the limit with express.json({ limit: '10mb' }) when needed — but always implement proper file upload handling with Multer (Chapter 23) instead of sending large files as base64 JSON.express.json() — JSON Body Parser
// Basic usage — required for every MERN REST API
app.use(express.json());
// With options
app.use(express.json({
limit: '1mb', // maximum body size (default: 100kb)
strict: true, // only accept arrays and objects (default: true)
// set to false to also accept primitives like numbers and strings
}));
// What it does:
// Before: req.body === undefined (raw body stream not yet parsed)
// After: req.body === { title: 'Hello', body: 'World' } (parsed JS object)
// How to verify it is working — test with Postman:
// POST /api/test Body: {"name": "Jane"} Content-Type: application/json
app.post('/api/test', (req, res) => {
console.log(req.body); // { name: 'Jane' }
console.log(typeof req.body); // 'object'
res.json({ received: req.body });
});
express.urlencoded() — Form Body Parser
// Parses HTML form submissions (Content-Type: application/x-www-form-urlencoded)
app.use(express.urlencoded({ extended: false }));
// extended: false → uses the built-in querystring module (simpler, flat objects)
// extended: true → uses the qs library (supports nested objects and arrays)
// For MERN APIs, extended: false is almost always sufficient
// Example: HTML form submits name=Jane&email=jane@example.com
// express.urlencoded() parses this into:
// req.body → { name: 'Jane', email: 'jane@example.com' }
// Using both parsers — supports both JSON APIs and HTML form submissions
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Express will use whichever parser matches the incoming Content-Type header
// JSON body → express.json() handles it → req.body populated
// Form body → express.urlencoded() handles it → req.body populated
// Neither → req.body remains undefined
express.static() — Serving Static Files
const path = require('path');
// Serve files from the 'public' directory
app.use(express.static(path.join(__dirname, 'public')));
// Files in public/ are served at the root URL:
// public/logo.png → GET /logo.png
// public/css/style.css → GET /css/style.css
// Serve with a URL prefix
app.use('/static', express.static(path.join(__dirname, 'public')));
// public/logo.png → GET /static/logo.png
// Serve uploaded files from the uploads directory
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
// uploads/photo.jpg → GET /uploads/photo.jpg
// With options
app.use(express.static(path.join(__dirname, 'public'), {
maxAge: '1d', // cache files for 1 day (Cache-Control header)
etag: true, // use ETags for cache validation (default: true)
dotfiles: 'ignore', // ignore dotfiles like .htaccess (default: 'ignore')
index: 'index.html', // default file for directories (default: 'index.html')
}));
Serving React’s Production Build from Express
// In production, Express can serve the built React app
// This is a common deployment pattern for MERN on a single server
const path = require('path');
// API routes — defined first, always take priority
app.use('/api/posts', require('./src/routes/posts'));
app.use('/api/auth', require('./src/routes/auth'));
// Serve React's built static files
if (process.env.NODE_ENV === 'production') {
// Serve the React build's static assets (JS, CSS, images)
app.use(express.static(path.join(__dirname, '../client/dist')));
// For any route not matched by the API, serve React's index.html
// React Router then handles client-side routing
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../client/dist', 'index.html'));
});
}
Common Mistakes
Mistake 1 — Not setting Content-Type: application/json in the client
❌ Wrong — POST request without the correct Content-Type header:
POST /api/posts
Content-Type: text/plain ← wrong
Body: {"title":"Hello"}
→ express.json() ignores this request — req.body is undefined
✅ Correct — Axios sets Content-Type: application/json automatically, but in Postman you must select Body → raw → JSON from the dropdown:
POST /api/posts
Content-Type: application/json ← correct
Body: {"title":"Hello"}
→ express.json() parses the body → req.body = { title: 'Hello' } ✓
Mistake 2 — Using express.static() to serve files that contain secrets
❌ Wrong — serving the entire project directory including .env:
app.use(express.static(path.join(__dirname))); // serves everything including .env!
✅ Correct — only serve a dedicated public directory that contains no sensitive files:
app.use(express.static(path.join(__dirname, 'public'))); // only public/ ✓
Mistake 3 — Putting express.static() before API routes
❌ Wrong — static middleware intercepts API paths if a file with that name exists:
app.use(express.static('public')); // if public/api.html exists → intercepts
app.use('/api', require('./routes')); // API routes may never be reached
✅ Correct — mount API routes first, then static middleware:
app.use('/api', require('./routes')); // API routes checked first ✓
app.use(express.static('public')); // static files only if no API route matched
Quick Reference
| Task | Code |
|---|---|
| Parse JSON bodies | app.use(express.json()) |
| Parse JSON (large limit) | app.use(express.json({ limit: '10mb' })) |
| Parse form bodies | app.use(express.urlencoded({ extended: false })) |
| Serve static files | app.use(express.static('public')) |
| Serve with URL prefix | app.use('/uploads', express.static('uploads')) |
| Serve React build | app.use(express.static('../client/dist')) |
| Cache control | express.static('public', { maxAge: '1d' }) |