Built-in Middleware

Difficulty: Beginner

Express ships with three built-in middleware functions that handle the most common request processing tasks: express.json() for parsing JSON request bodies, express.urlencoded() for parsing URL-encoded form data, and express.static() for serving static files. In older versions of Express, these were provided by the separate 'body-parser' package, but since Express 4.16+ they are built in.

express.json() parses incoming request bodies with a Content-Type of 'application/json'. When a client sends a POST or PUT request with a JSON body, this middleware reads the stream, parses the JSON string, and makes the resulting JavaScript object available on req.body. Without this middleware, req.body is undefined for JSON requests. You should register it with app.use(express.json()) before any routes that need to read JSON request bodies.

express.urlencoded() parses incoming request bodies with a Content-Type of 'application/x-www-form-urlencoded' - this is the default format used by HTML forms. When a form is submitted with method POST, the browser sends the form data in key=value&key=value format. This middleware parses it and populates req.body. The { extended: true } option allows parsing of nested objects using the qs library, while { extended: false } uses the simpler querystring library.

express.static() serves static files from a directory, as covered in the Static Files lesson. It is the only built-in middleware that does not process request bodies. Instead, it maps URL paths to file system paths and serves matching files with appropriate Content-Type headers.

All three built-in middleware functions accept an options object for customization. express.json() and express.urlencoded() accept options like 'limit' (maximum body size, default '100kb'), 'type' (which Content-Type values to parse), and 'verify' (a function to verify the raw body). Setting an appropriate limit is important for security - without it, a malicious client could send an enormous request body and consume all your server's memory.

Code examples

express.json() for API Endpoints

const express = require('express');
const app = express();

// Parse JSON bodies (limit to 10kb for security)
app.use(express.json({ limit: '10kb' }));

// Without express.json(), req.body would be undefined
app.post('/api/users', (req, res) => {
  console.log('Body:', req.body);
  console.log('Type:', typeof req.body);

  const { name, email } = req.body;

  if (!name || !email) {
    return res.status(400).json({ error: 'Name and email are required' });
  }

  res.status(201).json({
    id: 1,
    name,
    email,
    createdAt: new Date().toISOString()
  });
});

app.listen(3000, () => console.log('Server on port 3000'));

// Test with:
// curl -X POST http://localhost:3000/api/users \
//   -H "Content-Type: application/json" \
//   -d '{"name": "Alice", "email": "alice@example.com"}'

express.json() intercepts requests with Content-Type: application/json, reads the body stream, parses it with JSON.parse(), and sets req.body to the result. The limit option prevents denial-of-service attacks via oversized payloads.

express.urlencoded() for HTML Forms

const express = require('express');
const path = require('path');
const app = express();

// Parse URL-encoded bodies (from HTML forms)
app.use(express.urlencoded({ extended: true }));
app.use(express.json()); // Also parse JSON for API clients

// Serve a simple HTML form
app.get('/form', (req, res) => {
  res.send(`
    <form method="POST" action="/submit">
      <label>Name: <input type="text" name="name"></label><br>
      <label>Email: <input type="email" name="email"></label><br>
      <label>Age: <input type="number" name="age"></label><br>
      <button type="submit">Submit</button>
    </form>
  `);
});

// Handle form submission
app.post('/submit', (req, res) => {
  console.log('Form data:', req.body);
  // req.body = { name: 'Alice', email: 'alice@example.com', age: '25' }
  // Note: all values are strings!

  res.json({
    received: req.body,
    message: `Hello ${req.body.name}, your form was submitted!`
  });
});

app.listen(3000, () => console.log('Server on port 3000'));

HTML forms with method POST send data as URL-encoded (key=value&key=value). express.urlencoded() parses this into req.body. The { extended: true } option enables parsing of nested objects and arrays in form data.

Combining Built-in Middleware

const express = require('express');
const path = require('path');
const app = express();

// Register ALL built-in middleware
app.use(express.json({ limit: '10kb' }));            // JSON bodies
app.use(express.urlencoded({ extended: true }));       // Form bodies
app.use(express.static(path.join(__dirname, 'public'))); // Static files

// This route accepts BOTH JSON and form data
app.post('/api/contact', (req, res) => {
  const { name, email, message } = req.body;
  console.log('Content-Type:', req.headers['content-type']);
  console.log('Body:', req.body);

  if (!name || !email || !message) {
    return res.status(400).json({ error: 'All fields required' });
  }

  res.json({ success: true, data: { name, email, message } });
});

// Check what middleware parsed
app.post('/api/debug', (req, res) => {
  res.json({
    contentType: req.headers['content-type'],
    body: req.body,
    bodyType: typeof req.body,
    hasBody: !!req.body
  });
});

app.listen(3000, () => console.log('Server on port 3000'));

Most Express apps register all three built-in middleware. express.json() and express.urlencoded() only parse requests with matching Content-Type headers, so they coexist without conflict. A route can accept both JSON and form data seamlessly.

Key points

Concepts covered

express.json, express.urlencoded, express.static, Body Parsing, Form Data, Content-Type Handling