← All news
resilience4 min read

The request body size limit you raised to fix one route is now every route's problem

By Seaworthy · August 25, 2026

A rack of electronic server equipment in a dark room

Photo by Tyler on Unsplash

An API endpoint that accepts a JSON body usually looks like this, and nothing about it looks incomplete:

app.post('/api/documents', express.json(), async (req, res) => {
  const document = await saveDocument(req.body)
  res.json(document)
})

express.json() parses the request body into req.body before your handler runs. What it doesn't do, unless you tell it to, is stop parsing once the body gets unreasonably large.

The default body size limit is generous, and generous is the problem

body-parser (which express.json() wraps) defaults to a 100kb limit, which sounds like it's already handling this. In practice, that default gets overridden constantly, because plenty of legitimate features, a document editor, a bulk import endpoint, an image upload encoded as base64, genuinely need bodies larger than 100kb, and the fix people reach for is express.json({ limit: '50mb' }) applied broadly, often to the whole app rather than the one route that needs it.

Once that limit is raised app-wide, every endpoint, including ones that only ever expect a small payload like a login form or a search query, will happily read up to 50MB into memory before your handler code gets a chance to reject it. Send a 45MB JSON body to an endpoint that only expects { "query": "..." } and Node will parse the whole thing, allocate the memory for it, and only then hand it to your handler, which has no opportunity to say no earlier in the process. Do that with a handful of concurrent requests and you can exhaust available memory on an ordinary Node process without needing anything more sophisticated than curl and a big file.

This is a resource exhaustion issue, not a logic bug, which is part of why it's easy to miss in testing: every request you send manually while building the feature is a normal size, so the endpoint appears to work correctly right up until someone sends it something you didn't.

Size limits should match what the endpoint actually needs

// small, tightly scoped limit for ordinary API routes
app.use(express.json({ limit: '100kb' }))

// a separate, larger limit only on the route that actually needs it
app.post(
  '/api/documents/import',
  express.json({ limit: '10mb' }),
  async (req, res) => {
    const document = await saveDocument(req.body)
    res.json(document)
  }
)

Setting the limit per-route rather than globally means a single generous exception doesn't quietly widen the attack surface of every other endpoint in the app. The right number for any given route is whatever the largest legitimate payload actually is, with some headroom, not a round number picked to make an error message go away during development.

For file uploads specifically, a JSON-encoded body isn't the right transport at all, multipart/form-data with a library like multer streams the upload to disk or memory in chunks with its own size limits, rather than requiring the entire payload to be buffered as a JSON string first, which is both faster and safer for anything larger than a few hundred kilobytes.

It's also worth putting a limit in front of the application layer entirely. Nginx's client_max_body_size, or the equivalent on your load balancer or CDN, rejects oversized requests before they reach your Node process at all, which protects you even against routes where the application-level limit was set too generously or forgotten entirely.

client_max_body_size 1m;

Why the generous limit is the one that ships

limit: '50mb' shows up in code because it's the fix for a real, specific error, PayloadTooLargeError, thrown while building a feature that genuinely needs a large body. Raising the limit makes the error go away immediately and correctly for that one route. The mistake is scope: the fix gets applied to express.json() at the app level, where it's one line and doesn't require thinking about every other route in the file, rather than to the single route that actually needed it. Nothing in the error message or the fix distinguishes those two options, so the broader one, being less code, is the one that sticks.

Seaworthy flags express.json() or body-parser configured with a large body size limit at the application level rather than scoped to the specific routes that need it.

This article was generated by AI and summarises publicly available sources.

From the makers of Seaworthy

Seaworthy scans your repo for the issues covered in articles like this one.

Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.

$ npx seaworthycode