typicode/json-server · warning

Not Found

Error message

Not Found

What it means

This 404 is produced by the terminal middleware at src/app.ts:159-167 that every /:name route falls through to. Each handler stores its result in res.locals['data']; when that value is undefined the middleware answers {"error":"Not Found"}. undefined is the Service layer's signal that the named resource or the record with that id does not exist in the lowdb JSON file, so one response covers both an unknown collection and an unknown id.

Source

Thrown at src/app.ts:162

  app.put('/:name', withBody(service.update.bind(service)))

  app.put('/:name/:id', withIdAndBody(service.updateById.bind(service)))

  app.patch('/:name', withBody(service.patch.bind(service)))

  app.patch('/:name/:id', withIdAndBody(service.patchById.bind(service)))

  app.delete('/:name/:id', async (req, res, next) => {
    const { name = '', id = '' } = req.params
    res.locals['data'] = await service.destroyById(name, id, req.query['_dependent'])
    next?.()
  })

  app.use('/:name', (req, res) => {
    const { data } = res.locals
    if (data === undefined) {
      res.status(404).json({ error: 'Not Found' })
    } else {
      if (req.method === 'POST') res.status(201)
      res.json(data)
    }
  })

  return app
}

View on GitHub (pinned to 89a34a44b7)

Solutions

  1. Check that the resource name matches a top-level key in the db JSON file exactly, including pluralization and case
  2. List the collection first (GET /:name) and confirm the id exists before calling /:name/:id
  3. If the db file was reset or moved, re-seed it or point the app at the correct JSON file
  4. Treat 404 in the client as a normal 'resource absent' signal instead of retrying the same call

Example fix

// before
const res = await fetch('/posts/999')
if (!res.ok) throw new Error(String(res.status))

// after
const res = await fetch('/posts/999')
if (res.status === 404) {
  // record absent: create it or report to the user
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Probe for existence before acting on a specific id
const res = await fetch(`/posts/${encodeURIComponent(id)}`)
if (res.status === 404) {
  // create the record or skip, instead of issuing the write
}

Type guard

async function resourceExists(name: string, id: string): Promise<boolean> {
  const res = await fetch(`/${name}/${encodeURIComponent(id)}`)
  return res.ok
}
// usage: (await resourceExists('posts', id)) && await updateById(id, patch)

Try / catch

// This is an HTTP 404 response, not a thrown exception
try {
  const res = await fetch(`/posts/${id}`)
  if (!res.ok && res.status !== 404) throw new Error(`unexpected ${res.status}`)
  if (res.status === 404) {
    // absent collection or id: seed, recreate, or surface 'not found'
  }
} catch (e) {
  // network-level failure only; 404 is handled above
}

Prevention

When it happens

Trigger: GET /users when the db JSON has no "users" top-level key; GET /posts/999 or DELETE /posts/999 for an id that does not exist; a singular/plural mismatch such as GET /post instead of /posts; a case mismatch such as /Posts, since the resource name is matched exactly against the db key.

Common situations: Client and server disagree on the resource name (typo, singular vs plural, casing); the lowdb JSON file was reset, moved, or pointed at the wrong path so previously known ids vanish; the record was deleted by another client between read and write; tests run against a fresh empty db expecting seeded fixtures.


AI-assisted analysis of typicode/json-server@89a34a44b7 (2026-08-25). Data as JSON: /api/errors/ab48a00e0b363b72. Report an issue: GitHub.