Built-in Express Middleware — express.json, express.urlencoded and express.static

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
Note: Before Express 4.16.0, JSON and URL-encoded body parsing required the separate 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.
Tip: For a MERN REST API that communicates exclusively with React via Axios, you only need 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.
Warning: By default 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' })

🧠 Test Yourself

A POST request to /api/posts from an HTML form (Content-Type: application/x-www-form-urlencoded) arrives at your Express server. You have app.use(express.json()) but NOT app.use(express.urlencoded()). What is req.body?