Handling POST Requests and JSON Bodies

beginner
12 min

Handling POST Requests and JSON Bodies

So far our servers have only answered questions. Real applications also accept data: signup forms, comments, orders, uploaded settings. That data arrives in the request body, and this lesson shows you how to read it safely with the built-in parsers, build a small in-memory API, and validate what clients send.

Why You Need a Body Parser

HTTP bodies arrive as a raw stream of bytes. Express does not guess the format; you opt in to parsing with middleware. The two built-in parsers cover the common cases:

js
const express = require('express'); const app = express(); app.use(express.json()); // application/json app.use(express.urlencoded({ extended: true })); // HTML form posts

With these registered near the top of your file, any request whose Content-Type matches is parsed and the result is placed on req.body. Without them, req.body is undefined, which is the most googled Express beginner error.

Receiving JSON

Modern frontends send JSON via fetch or axios. Here is a create endpoint for a to-do list:

js
const todos = []; let nextId = 1; app.post('/api/todos', (req, res) => { const { title } = req.body; if (!title || typeof title !== 'string' || !title.trim()) { return res.status(400).json({ error: 'title is required' }); } const todo = { id: nextId++, title: title.trim(), done: false }; todos.push(todo); res.status(201).json(todo); }); app.get('/api/todos', (req, res) => res.json(todos));

Two habits to notice: validate before using the body, and answer a successful create with status 201 plus the created object so the client learns the new ID.

Testing with curl

You can exercise the endpoint from a terminal:

bash
curl -X POST http://localhost:3000/api/todos \ -H 'Content-Type: application/json' \ -d '{"title": "Learn Express"}'

The Content-Type header matters. If you omit it, express.json() skips the body and req.body stays empty. Postman and Insomnia set it for you when you choose the JSON body type.

Handling HTML Form Posts

Classic server-rendered forms send application/x-www-form-urlencoded data. The urlencoded parser turns fields into req.body properties:

html
<form action="/subscribe" method="POST"> <input name="email" type="email" required> <button>Subscribe</button> </form>
js
app.post('/subscribe', (req, res) => { const email = (req.body.email || '').trim(); if (!email.includes('@')) { return res.status(400).send('Please provide a valid email.'); } res.send('Subscribed: ' + email); });

The extended: true option lets the parser understand nested objects like user[name]; for flat forms either setting works.

Updating and Deleting

POST creates, but bodies also power updates. A PATCH endpoint completes the picture:

js
app.patch('/api/todos/:id', (req, res) => { const todo = todos.find(t => t.id === Number(req.params.id)); if (!todo) return res.status(404).json({ error: 'Not found' }); if (typeof req.body.done === 'boolean') todo.done = req.body.done; res.json(todo); });

Limits and Safety

express.json accepts bodies up to 100kb by default. Raise or lower it deliberately with app.use(express.json({ limit: '10kb' })) to blunt abuse. Also remember that in-memory arrays vanish on restart; they are perfect for learning and prototypes, and lesson 10 replaces them with MongoDB.

Key Takeaways

  • Register express.json() and express.urlencoded() before your routes.
  • Parsed data appears on req.body only when the Content-Type matches.
  • Validate every field and respond 400 with a clear message when input is bad.
  • Use 201 for successful creation and echo the created resource.
  • Set a body size limit appropriate to your API.

Next lesson: Express Router and Project Structure, where we split this growing file into clean modules.