# json-server User Guide

`json-server` turns any JSON file into a full fake REST API in seconds. Point it at a file like `db.json`, and every top-level key becomes an endpoint that supports `GET`, `POST`, `PUT`, `PATCH`, and `DELETE`. It is the canonical zero-config mock backend for frontend developers, integration tests, demos, prototypes, and offline development.

This guide covers the `1.0.0-beta.15` release: a TypeScript rewrite built on `@tinyhttp`, `lowdb`, and Node's native HTTP. It runs as a single command, requires no global install, and persists every change you make through the API directly to the JSON file on disk.

---

## 1. Quick Start

The fastest possible path: one command, one file, a working REST API.

```bash
npx json-server db.json
```

`npx` downloads and runs `json-server` for you the first time. No global install, no `package.json`, no setup. As long as Node.js 22.12 or newer is on your machine, this works.

> [!TIP]
> If you don't have a `db.json` yet, create one in the same directory:
> ```json
> {
>   "posts": [
>     { "id": "1", "title": "Hello world", "views": 100 }
>   ],
>   "comments": [
>     { "id": "1", "text": "Nice post", "postId": "1" }
>   ],
>   "profile": { "name": "Alice" }
> }
> ```
> The file is required — `json-server` will not start without one. An empty file is fine; it is initialized to `{}` on first run.

You'll see output like:

```text
JSON Server started on PORT :3000
Press CTRL-C to stop
Watching db.json...

♡⸜(˶˃ ᵕ ˂˶)⸝♡

Index:
http://localhost:3000/

Endpoints:
http://localhost:3000/posts
http://localhost:3000/comments
http://localhost:3000/profile
```

Open another terminal and exercise the API:

```bash
curl http://localhost:3000/posts
```

```json
[
  { "id": "1", "title": "Hello world", "views": 100 }
]
```

```bash
curl http://localhost:3000/posts/1
```

```json
{ "id": "1", "title": "Hello world", "views": 100 }
```

Create a record:

```bash
curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"Second post","views":0}'
```

```json
{ "title": "Second post", "views": 0, "id": "h7Ks93Lq2nE" }
```

That row is now persisted in `db.json` on disk. Stop the server, reopen the file, and you'll see the new post.

> [!NOTE]
> The server auto-watches `db.json` while running (in non-production mode). Edit the file in your editor, save it, and the API picks up the change instantly — no restart needed.

### What you can do next

- Mock a paginated list (`?_page=1&_per_page=10`) — see Section 5
- Filter records (`?title:eq=hello`, `?views:gt=100`) — see Section 4
- Embed related records (`?_embed=comments`) — see Section 4
- Move beyond port 3000 (`-p 8080`) — see Section 3

---

## 2. Installation

`json-server` ships as a single npm package. Pick whichever method fits how your project is set up.

### Method 1: One-shot via npx (recommended)

```bash
npx json-server db.json
```

This is the path the project tutorial leads with, and it's right for almost every use. `npx` fetches the package, runs it once against your `db.json`, and exits when you stop the server. No files added to your project, no global pollution.

> [!TIP]
> The first `npx` invocation downloads and caches the package (a few seconds). Subsequent runs from the cache are near-instant.

### Method 2: Global install

```bash
npm install -g json-server
json-server db.json
```

Useful if you reach for `json-server` constantly across many projects and prefer a permanent `json-server` command on your `PATH`.

> [!IMPORTANT]
> Global installs require Node.js **22.12.0 or newer**. The `engines.node` field in the package's `package.json` enforces this — older Node versions emit an `EBADENGINE` warning and may not run at all. Check with `node --version` before installing.

### Method 3: Project-local dependency

When `json-server` is part of your dev workflow (running tests, simulating an API in CI), add it as a `devDependency`:

```bash
npm install --save-dev json-server
```

Then call it from a script in your `package.json`:

```json
{
  "scripts": {
    "mock": "json-server db.json"
  }
}
```

```bash
npm run mock
```

This is the right method for a team — every developer gets the same version pinned in `package-lock.json`.

### Method 4: Build from source

For contributors or anyone who wants to run a development checkout:

```bash
git clone https://github.com/typicode/json-server.git
cd json-server
npm install
npm run dev -- fixtures/db.json
```

The `dev` script runs the TypeScript source directly via `node --watch --experimental-strip-types src/bin.ts`. To produce the published JavaScript:

```bash
npm run build      # rm -rf lib && tsc
node lib/bin.js fixtures/db.json
```

### System requirements

| Requirement | Value | Notes |
|-------------|-------|-------|
| Node.js | `>=22.12.0` | Enforced by `package.json` `engines.node`. |
| OS | macOS, Linux, Windows | Pure JavaScript — no native bindings. |
| Disk | < 30 MB | Most of that is `node_modules`. |
| Memory | Modest | The whole DB is held in memory; size your DB accordingly. |
| Network | None at runtime | Only needed for the initial install. |

### Verifying the install

```bash
json-server --version
```

```text
1.0.0-beta.15
```

```bash
json-server --help
```

```text
Usage: json-server [options] <file>

Options:
  -p, --port <port>  Port (default: 3000)
  -h, --host <host>  Host (default: localhost)
  -s, --static <dir> Static files directory (multiple allowed)
  --help             Show this message
  --version          Show version number
```

> [!WARNING]
> The current `1.0.0-beta.15` line is a rewrite. Several heavyweight features from `0.x` (custom routes, middleware files, `--delay`, the `--routes` option) are **not present** in this beta. If you depend on those, pin to `0.17.4` from the `0.x` line until they reappear. See the FAQ for what's missing.

---

## 3. Configuration

`json-server` is intentionally minimal. There is no config file — everything is set via CLI flags or environment variables, and almost everything has a sensible default.

### Command-line flags

| Flag | Short | Type | Default | Description |
|------|-------|------|---------|-------------|
| `--port <port>` | `-p` | number | `3000` (or `$PORT`) | TCP port to listen on. |
| `--host <host>` | `-h` | string | `localhost` (or `$HOST`) | Host/interface to bind. |
| `--static <dir>` | `-s` | string (repeatable) | `[]` | Extra directory of static files to serve. May be passed multiple times. |
| `--help` | — | boolean | — | Print help and exit. |
| `--version` | — | boolean | — | Print version and exit. |
| `--watch` | `-w` | boolean | — | **Deprecated and ignored** — file watching is on by default. Prints a yellow warning if used. |

> [!NOTE]
> The positional argument (`<file>`) is required. It must be a `.json` or `.json5` file. If the file doesn't exist, the server exits with `File <name> not found`. If it exists but is empty, it is initialized with `{}` on startup.

### Environment variables

| Variable | Purpose | Default |
|----------|---------|---------|
| `PORT` | Default for `-p`. CLI `-p` wins if both are set. | `3000` |
| `HOST` | Default for `-h`. CLI `-h` wins if both are set. | `localhost` |
| `NODE_ENV` | When set to `production`, disables file watching, enables view caching, and disables `sirv` dev mode for static files. | (unset) |

```bash
PORT=8080 HOST=0.0.0.0 npx json-server db.json
```

### Common option combinations

| Goal | Command |
|------|---------|
| Default everything | `npx json-server db.json` |
| Custom port | `npx json-server -p 8080 db.json` |
| Bind to all interfaces (LAN) | `npx json-server -h 0.0.0.0 db.json` |
| Serve a custom static dir alongside the API | `npx json-server -s ./assets db.json` |
| Two static dirs | `npx json-server -s ./public2 -s ./images db.json` |
| Production-mode (no file watch, view caching) | `NODE_ENV=production npx json-server db.json` |
| JSON5 source file | `npx json-server db.json5` |

> [!TIP]
> See the Admin Guide's Configuration Reference for full details on every flag, environment variable, the JSON Schema enforced on `db.json`, and the production-mode behavior changes.

### The database file

Your JSON file IS your configuration of resources. Each top-level key becomes a route:

```json
{
  "posts":    [ { "id": "1", "title": "..." } ],
  "comments": [ { "id": "1", "postId": "1" } ],
  "profile":  { "name": "Alice" }
}
```

This produces:

| Key | Type | Routes generated |
|-----|------|------------------|
| `posts` | array | `/posts`, `/posts/:id` (full CRUD) |
| `comments` | array | `/comments`, `/comments/:id` (full CRUD) |
| `profile` | object | `/profile` (singleton — only `GET`, `PUT`, `PATCH`) |

> [!IMPORTANT]
> Items in arrays must have an `id` field (string). On read, numeric `id`s are automatically coerced to strings, and items missing an `id` get a random base64url ID generated at load time and written back to the file. This is enforced by the normalizing adapter on every read.

`json-server` also auto-injects a `$schema` pointer into your file on every write, pointing at `./node_modules/json-server/schema.json`. This lets editors with JSON Schema support autocomplete and validate the file shape. It is stripped on read so it never appears in API responses.

```json
{
  "$schema": "./node_modules/json-server/schema.json",
  "posts":   [ { "id": "1" } ]
}
```

### JSON5 support

If your file ends in `.json5`, comments, trailing commas, and unquoted keys are parsed correctly — both on read and on write.

```json5
{
  // posts table
  posts: [
    { id: '1', title: 'Hello' },
  ],
}
```

```bash
npx json-server db.json5
```

---

## 4. Core Usage

Every top-level key in your JSON file becomes an HTTP resource. The behavior depends on whether the value is an **array** (collection) or a non-array **object** (singleton).

### The HTTP surface

| Method | Path | Behavior on collection (`posts: [...]`) | Behavior on singleton (`profile: {...}`) |
|--------|------|-----------------------------------------|------------------------------------------|
| `GET` | `/:name` | Returns array, supports filter/sort/page/embed | Returns the object |
| `GET` | `/:name/:id` | Returns one item or `404` | `404` (no `:id` on singletons) |
| `POST` | `/:name` | Creates a new item, returns `201` with generated `id` | `404` |
| `PUT` | `/:name` | `404` (use `:id` form) | Replaces the singleton, returns it |
| `PUT` | `/:name/:id` | Replaces item by id (preserving `id`) | `404` |
| `PATCH` | `/:name` | `404` (use `:id` form) | Merges into the singleton |
| `PATCH` | `/:name/:id` | Merges fields into item by id | `404` |
| `DELETE` | `/:name/:id` | Deletes item, nullifies foreign keys, optionally cascades | `404` |
| `OPTIONS` | `*` | CORS pre-flight, returns `204` | Same |

> [!NOTE]
> Asking for a key that doesn't exist in the file returns `{"error": "Not Found"}` with status `404`. POSTing or PATCHing a body that isn't a JSON object returns `{"error": "Body must be a JSON object"}` with status `400`.

### Reading data

#### List a collection

```bash
curl http://localhost:3000/posts
```

```json
[
  { "id": "1", "title": "a", "views": 100 },
  { "id": "2", "title": "b", "views": 200 }
]
```

#### Get one item

```bash
curl http://localhost:3000/posts/1
```

```json
{ "id": "1", "title": "a", "views": 100 }
```

A non-existent `:id` returns `404`:

```bash
curl -i http://localhost:3000/posts/-1
```

```text
HTTP/1.1 404 Not Found
Content-Type: application/json

{"error":"Not Found"}
```

### Filtering with `where` operators

Pass query parameters to filter a collection. The format is `field:operator=value` (preferred) or the legacy `field_operator=value` (kept for `0.17` compatibility). A bare `field=value` is treated as `field:eq=value`.

| Operator | Meaning | Example | Matches |
|----------|---------|---------|---------|
| `eq` | equals (default) | `?title:eq=hello` or `?title=hello` | `title === "hello"` |
| `ne` | not equals | `?title:ne=hello` | `title !== "hello"` |
| `lt` | less than | `?views:lt=100` | `views < 100` |
| `lte` | less than or equal | `?views:lte=100` | `views <= 100` |
| `gt` | greater than | `?views:gt=100` | `views > 100` |
| `gte` | greater than or equal | `?views:gte=100` | `views >= 100` |
| `in` | in list (comma-separated) | `?id:in=1,2,3` | `id` in `["1","2","3"]` |
| `contains` | string includes (case-insensitive) | `?title:contains=ello` | `title` contains "ello" |
| `startsWith` | string starts with (case-insensitive) | `?title:startsWith=hel` | `title` starts with "hel" |
| `endsWith` | string ends with (case-insensitive) | `?title:endsWith=lo` | `title` ends with "lo" |

Values are auto-coerced: `true`/`false` become booleans, `null` becomes null, anything that parses as a finite number becomes a number, otherwise it stays a string.

Combine filters by repeating params:

```bash
curl 'http://localhost:3000/posts?views:gt=100&published:eq=true'
```

Filter on nested fields with dot notation:

```bash
curl 'http://localhost:3000/posts?author.name:eq=Alice'
```

For `OR` logic across predicates, use `_where` with a JSON object (see below).

> [!TIP]
> For complex filters, the `_where` JSON-query parameter is much cleaner. Pass a URL-encoded JSON object and it overrides any other filter params:
> ```
> ?_where={"author":{"name":{"eq":"Alice"}},"views":{"gt":100}}
> ```

### Sorting

```bash
curl 'http://localhost:3000/posts?_sort=views'        # ascending
curl 'http://localhost:3000/posts?_sort=-views'       # descending
curl 'http://localhost:3000/posts?_sort=views,-title' # multi-key
```

The `_sort` parameter accepts a comma-separated list of fields; prefix with `-` for descending.

### Pagination

```bash
curl 'http://localhost:3000/posts?_page=1&_per_page=10'
```

```json
{
  "first": 1,
  "prev": null,
  "next": 2,
  "last": 3,
  "pages": 3,
  "items": 25,
  "data": [
    { "id": "1", "title": "..." }
  ]
}
```

The response wraps the page slice in a navigation object: `first`, `prev`, `next`, `last`, total `pages`, total `items`, and the page slice in `data`.

| Param | Default | Notes |
|-------|---------|-------|
| `_page` | (unset = no pagination) | Page number, 1-indexed. Clamped between 1 and `last`. |
| `_per_page` | `10` (when `_page` is set) | Per-page count. Clamped to a minimum of 1. |

> [!NOTE]
> Pagination is opt-in: a request without `_page` returns the full filtered array, not a wrapped object. Pass `_page=1` (with or without `_per_page`) to get the paginated shape.

### Embedding related records

`json-server` infers relationships from foreign-key naming. If `comments` items have a `postId` field, then:

```bash
curl 'http://localhost:3000/posts/1?_embed=comments'
```

```json
{
  "id": "1",
  "title": "a",
  "comments": [
    { "id": "1", "postId": "1", "text": "Nice" }
  ]
}
```

And the reverse direction (singular form embeds the parent):

```bash
curl 'http://localhost:3000/comments/1?_embed=post'
```

```json
{
  "id": "1",
  "postId": "1",
  "post": { "id": "1", "title": "a" }
}
```

The rule:
- `_embed=children` (plural) — finds items in `children` whose `<singular_parent>Id` equals this item's `id`.
- `_embed=parent` (singular) — finds the item in the `parents` resource whose `id` matches this item's `parentId`.

`_embed` may be repeated (`?_embed=comments&_embed=author`) to embed multiple relations.

### Writing data

#### Create

```bash
curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"New post","views":0}'
```

```json
{ "title": "New post", "views": 0, "id": "Hk7Lq3nW" }
```

A new random `id` is always assigned — any `id` you submit is overwritten (preventing client-side id forgery). Status `201 Created` on success.

#### Replace by id (`PUT`)

```bash
curl -X PUT http://localhost:3000/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"title":"Replaced","views":0}'
```

The whole record is replaced, but the original `id` is preserved.

#### Patch by id (`PATCH`)

```bash
curl -X PATCH http://localhost:3000/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"views":999}'
```

Merges the request body into the existing record, leaving unspecified fields untouched.

#### Delete by id

```bash
curl -X DELETE http://localhost:3000/posts/1
```

Returns the deleted item, or `404` if it doesn't exist. **Side effect:** any other items in the database that have a foreign key pointing to this `id` (e.g. `comments` with `postId: "1"`) get that key set to `null`.

To cascade-delete dependents instead of nullifying:

```bash
curl -X DELETE 'http://localhost:3000/posts/1?_dependent=comments&_dependent=tags'
```

Each `_dependent` names a resource whose foreign-key-bearing rows should be deleted along with the post.

> [!WARNING]
> Every successful write (`POST`/`PUT`/`PATCH`/`DELETE`) flushes the in-memory database back to your JSON file on disk. There is no undo. Back up `db.json` before running destructive scripts.

### Error responses

| Status | Body | When |
|--------|------|------|
| `200 OK` | The data | Successful read or update |
| `201 Created` | The new item | Successful `POST` to a collection |
| `204 No Content` | empty | CORS pre-flight (`OPTIONS`) |
| `400 Bad Request` | `{"error":"Body must be a JSON object"}` | `POST`/`PUT`/`PATCH` with a non-object body (array, string, null) |
| `404 Not Found` | `{"error":"Not Found"}` | Unknown resource, missing id, or operation not supported on that resource shape |

### Static files & the home page

While `json-server` is running:

- `http://localhost:3000/` renders an HTML index page (the bundled `views/index.html` Eta template) listing your endpoints with a friendly preview.
- `http://localhost:3000/<file>` serves anything in `./public/` (auto), and any directories you passed via `-s`.

```bash
mkdir public
echo "<h1>Hi</h1>" > public/test.html
npx json-server db.json
```

Then `curl http://localhost:3000/test.html` returns the HTML.

> [!TIP]
> Use `-s` to point at your frontend's `dist/` folder during development — `json-server` serves your built SPA on `/` and the API on `/<resource>` from the same origin (no CORS pain).

---

## 5. Common Tasks

Real-world recipes. Each one is self-contained — pick the one that matches your problem.

### Task 1: Stand up a fake REST API for a frontend in 60 seconds

**Goal:** Your React/Vue/Svelte/Solid app needs a backend, and the real one isn't ready yet.

**Steps:**

1. Drop a `db.json` next to your frontend project:
   ```json
   {
     "users": [
       { "id": "1", "name": "Alice", "email": "[email removed]" }
     ],
     "todos": [
       { "id": "1", "userId": "1", "title": "Buy milk", "done": false }
     ]
   }
   ```

2. Start the server bound to all interfaces and a non-conflicting port:
   ```bash
   npx json-server -h 0.0.0.0 -p 4000 db.json
   ```

3. In your frontend, point your fetch base URL at `http://localhost:4000`.

4. Develop. Every `POST`/`PATCH`/`DELETE` writes through to `db.json` so your data is sticky across server restarts.

> [!TIP]
> Add `db.json` to `.gitignore` if it accumulates test data, or check it in if it's seed data your team needs.

### Task 2: Mock a paginated list

**Goal:** You're building an infinite-scroll component and need realistic page metadata.

Seed enough rows:

```json
{
  "posts": [
    { "id": "1", "title": "Post 1" },
    { "id": "2", "title": "Post 2" },
    { "id": "3", "title": "Post 3" }
  ]
}
```

Request page 1 with 2 items per page:

```bash
curl 'http://localhost:3000/posts?_page=1&_per_page=2'
```

```json
{
  "first": 1, "prev": null, "next": 2, "last": 2,
  "pages": 2, "items": 3,
  "data": [ { "id":"1","title":"Post 1" }, { "id":"2","title":"Post 2" } ]
}
```

Use `next` to drive infinite scroll. When `next` is `null`, you've hit the end.

> [!NOTE]
> Pagination clamps gracefully: `_page=999` with 3 items returns the last available page (clamped to `last`); `_per_page=0` is clamped to `1`. You will never see a `400` from out-of-range pagination params.

### Task 3: Filter and sort like a real backend

**Goal:** Demonstrate search-as-you-type or a sortable table without writing a query DSL.

```bash
# All published posts with views over 100, newest first
curl 'http://localhost:3000/posts?published:eq=true&views:gt=100&_sort=-createdAt'

# Posts whose title contains "react", page 1
curl 'http://localhost:3000/posts?title:contains=react&_page=1&_per_page=20'

# Posts by Alice OR Bob
curl 'http://localhost:3000/posts?author.name:in=Alice,Bob'
```

For complex predicates, build a JSON object on the client and send it as `_where`:

```javascript
const where = {
  or: [
    { author: { name: { eq: 'Alice' } } },
    { tags: { contains: 'featured' } }
  ],
  views: { gt: 100 }
}
const url = `http://localhost:3000/posts?_where=${encodeURIComponent(JSON.stringify(where))}`
```

### Task 4: Embed parent and child relationships

**Goal:** Your UI shows posts with their comments inline, or comments with their author inline.

Seed:

```json
{
  "posts":    [ { "id": "1", "title": "Hello", "userId": "10" } ],
  "comments": [ { "id": "1", "postId": "1", "text": "Nice" } ],
  "users":    [ { "id": "10", "name": "Alice" } ]
}
```

Embed children:

```bash
curl 'http://localhost:3000/posts/1?_embed=comments'
```

Embed a parent:

```bash
curl 'http://localhost:3000/comments/1?_embed=post'
```

Stack relations:

```bash
curl 'http://localhost:3000/posts?_embed=comments&_embed=user'
```

> [!NOTE]
> Embed relies on naming convention: a child resource's foreign key must be `<singular_parent>Id`. `comments[*].postId` works because `inflection.singularize("posts") === "post"`. If your foreign keys don't match this pattern, embed silently returns the item unchanged.

### Task 5: Persist changes through to a JSON5 file with comments

**Goal:** You want your fixture file to keep human-readable comments and trailing commas.

Name your file `db.json5`:

```json5
{
  // Top-level posts table
  posts: [
    { id: '1', title: 'Hello' },
  ],
  // Singleton — site-wide profile
  profile: { name: 'Alice' },
}
```

```bash
npx json-server db.json5
```

JSON5 syntax is preserved on writes. The adapter uses `JSON5.parse` and `JSON5.stringify` automatically when the extension is `.json5`.

### Task 6: Serve a frontend bundle alongside the API

**Goal:** Run a single command that hosts both your built SPA and its mock API.

```bash
npx json-server -s ./dist db.json
```

- `http://localhost:3000/index.html` (and any other static files) → served from `./dist/`
- `http://localhost:3000/posts` → API
- `http://localhost:3000/` → unless you also have a `views/index.html`, the bundled landing page renders

You can pass `-s` multiple times to layer directories:

```bash
npx json-server -s ./dist -s ./assets -s ./uploads db.json
```

### Task 7: Cascade-delete or orphan related records

**Goal:** Decide what happens to comments when their parent post is deleted.

By default, `DELETE /posts/1` nullifies foreign keys: `comments` whose `postId` was `"1"` now have `postId: null`. The comments still exist, just orphaned.

To delete those dependents instead:

```bash
curl -X DELETE 'http://localhost:3000/posts/1?_dependent=comments'
```

Multiple dependents:

```bash
curl -X DELETE 'http://localhost:3000/posts/1?_dependent=comments&_dependent=likes'
```

This is the closest thing to a `ON DELETE CASCADE` policy `json-server` offers.

### Task 8: Run on LAN so a phone or another machine can reach it

**Goal:** Test your mobile UI against the mock API from a real device.

```bash
HOST=0.0.0.0 PORT=4000 npx json-server db.json
```

Then point your phone's browser (or build) at `http://<your-laptop-ip>:4000`. CORS is enabled for any `Origin`, so cross-origin requests Just Work.

> [!CAUTION]
> Binding to `0.0.0.0` exposes the API to anyone on your network. There is no auth — they can read and write your `db.json` freely. Use it on trusted networks only, or keep the default `localhost`. See the Admin Guide's Security section for hardening notes.

---

## 6. Troubleshooting

When something goes wrong, work the list top to bottom — most issues are install or configuration drift.

### Decision tree

```text
Server won't start?            -> Node version, file path, port conflict (below)
Server starts, returns 404?    -> Resource name typo, plural/singular mismatch
Returns 400 on POST/PUT?       -> Body shape: must be a JSON object, not array/string/null
Watcher doesn't pick up edits? -> NODE_ENV=production disables watch
Embed returns nothing?         -> Foreign-key naming mismatch (must be `<singular>Id`)
Filter returns wrong data?     -> Operator name typo (silent ignore — see #1731)
```

### Error: `File db.json not found`

**Cause:** The positional argument points at a path that doesn't exist on disk.

**Solution:**

1. Verify the path: `ls db.json`.
2. If the file should be created from scratch, `echo '{}' > db.json` first.
3. Re-run with the correct path.

**Prevention:** Always pass an absolute or working-directory-relative path. A bare filename is resolved against the current shell directory, not the user's home.

### Error: `EBADENGINE Unsupported engine ... required: { node: '>=22.12.0' }`

**Cause:** The installed Node.js version is older than 22.12.

**Solution:**

```bash
node --version           # check current
# upgrade via nvm:
nvm install 22
nvm use 22
```

**Prevention:** Pin Node version in your project's `.nvmrc` or `package.json` `engines` block.

### Error: `EADDRINUSE: address already in use :::3000`

**Cause:** Another process holds port 3000.

**Solution:**

1. Find the squatter: `lsof -i :3000` (macOS/Linux) or `netstat -ano | findstr :3000` (Windows).
2. Either kill it, or pick another port: `npx json-server -p 4000 db.json`.

**Prevention:** Standardize one port per project in your `npm run mock` script.

### Error: `{"error": "Body must be a JSON object"}`

**Cause:** You sent an array, string, `null`, or no body to `POST`/`PUT`/`PATCH`. The endpoint validates with the internal `isItem()` check, which only accepts non-array objects.

**Solution:**

```bash
# Wrong:
curl -X POST http://localhost:3000/posts -d '[{"title":"x"}]' ...

# Right:
curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"x"}'
```

**Prevention:** Always send `Content-Type: application/json` and a single JSON object, never an array. To bulk-insert, loop client-side.

### Error: `{"error":"Not Found"}` on a resource you can see in `db.json`

**Cause:** Resource name typo, or you're hitting `/posts/1` when `posts` is a singleton object (no `:id` route on singletons), or vice versa.

**Solution:**

1. Check the indexed routes printed at startup — that's the authoritative list.
2. Confirm whether the resource is a collection (array, supports `:id`) or a singleton (object, only top-level methods).
3. Check method/route compatibility — `PUT /:name` only works on singletons; `PUT /:name/:id` only on collections.

**Prevention:** Prefer plural names for collection resources (`posts`, `comments`). Embed inference depends on it.

### File-watcher not picking up edits (issue [#286](https://github.com/typicode/json-server/issues/286), behavior in `1.0.0-beta.15`)

**Cause:** `NODE_ENV=production` disables the chokidar file watcher entirely. Or your editor is writing to a temp file and renaming, which some watchers miss.

**Solution:**

```bash
echo $NODE_ENV
# (Should be empty.)

unset NODE_ENV
npx json-server db.json
```

**Prevention:** Don't set `NODE_ENV=production` in your shell profile. Set it only in CI / deployment, where it belongs.

### `--delay` does nothing (issues [#1558](https://github.com/typicode/json-server/issues/1558), [#1545](https://github.com/typicode/json-server/issues/1545))

**Cause:** The `--delay` flag does **not exist** in `1.0.0-beta.15`. It was a `0.x` feature, removed during the rewrite. No rate-limit-simulation flags exist.

**Solution:** If you need latency simulation, run a small wrapper script. The simplest pattern: a Node `http` proxy in front of `json-server` that adds a `setTimeout` before forwarding. Or pin to `0.17.4` from the `0.x` line.

**Prevention:** Read the `--help` output before relying on a flag from older docs.

### Embed returns the item unchanged (issue [#1634](https://github.com/typicode/json-server/issues/1634))

**Cause:** Foreign-key naming doesn't match the convention. `_embed=comments` looks for `posts[*].id` and `comments[*].postId`. If your column is named `post_id` or `parent`, embed gives up silently.

**Solution:**

1. Rename the foreign key to `<singular_parent>Id` (camelCase, capital `I`).
2. Or post-process on the client.

**Prevention:** Stick to the `xxxId` naming convention from day one.

### Auto-IDs that look numeric break exact-match queries (issue [#1734](https://github.com/typicode/json-server/issues/1734))

**Cause:** Random IDs are 8 random bytes encoded as base64url. Occasionally that produces an all-digit string like `"40123456"`. When you query with a numeric string, the value coercer turns it into a `Number`, and `===` against the stored string mismatches.

**Solution:** Be aware of the type when filtering — `?id:eq=40123456` compares a coerced `Number` against a stored string. Filtering by a different field (e.g. a slug) is more reliable.

**Prevention:** Use a string field that's known to be non-numeric (a slug, UUID, or human-readable key) as your stable lookup column.

### Pagination at `_per_page=0` (issue [#1721](https://github.com/typicode/json-server/issues/1721))

**Cause:** `_per_page=0` is clamped to `1` (not an error), which can be unexpected if you're relying on `0` for "no items".

**Solution:** Don't pass `_per_page=0`. Omit `_page` entirely if you want the unwrapped full array.

**Prevention:** Validate pagination params on the client before sending.

### Watcher doesn't trigger when running in a dev container

**Cause:** Some bind-mount filesystems (Docker on macOS, WSL2) don't propagate inotify events.

**Solution:** Edit the file from inside the container, or run `json-server` outside the container and expose its port.

**Prevention:** For containerized workflows, prefer running `json-server` outside the container.

---

## 7. FAQ

### General

**Q: What versions does this guide cover?**

`1.0.0-beta.15` of `typicode/json-server` (commit `89a34a4`). This is the post-rewrite line. Behavior here may differ substantially from `0.17.x` and earlier — those versions had custom routes, middleware, `--delay`, and other features that aren't in the beta yet.

**Q: Does `json-server` need a database (Postgres, MySQL, etc.)?**

No. The "database" is your JSON file, loaded into memory at startup via `lowdb`. Every write flushes back to disk through `steno` (atomic-ish writes).

**Q: How big can my `db.json` get before performance suffers?**

Practical limit: tens of MB. The whole file lives in memory, every write rewrites it, and filtering is a linear scan. For demos and prototyping this is invisible. For production-volume datasets it is the wrong tool.

### Routing & shape

**Q: Why does `POST /posts/1` return `404`?**

`POST` only exists on collections (`POST /:name`), never on individual items. Use `PATCH /posts/1` to merge fields, or `PUT /posts/1` to replace the record.

**Q: Why does `PUT /posts` return `404`?**

`PUT` on a name without an id is reserved for **singleton** resources (a top-level non-array object). For arrays, you must specify which item via `PUT /posts/:id`.

**Q: How do I get a list of all my endpoints?**

The startup banner prints them. Or `GET /` renders the bundled HTML index page.

### Filtering

**Q: How do I do an `OR` filter?**

Use `_where` with a JSON object containing an `or` key: `?_where={"or":[{"a":{"eq":1}},{"b":{"eq":2}}]}` (URL-encoded). The query-param syntax has no `OR`; only `AND` between params.

**Q: Are filters case-sensitive?**

`eq`/`ne`/`in` use strict JavaScript `===`/`!==` — case-sensitive for strings. `contains`/`startsWith`/`endsWith` are explicitly case-**insensitive** (the field and the search value are lowercased before comparison).

**Q: What happens if I use an unknown operator like `:foo`?**

The filter is silently dropped. `?title:foo=bar&title:eq=hello` behaves as `?title:eq=hello`. This can hide bugs — see issue [#1731](https://github.com/typicode/json-server/issues/1731). Stick to the documented operator list.

### IDs and writes

**Q: Can I assign my own `id` on `POST`?**

No — any `id` you submit is overwritten with a randomly generated one. This is by design (prevents id collisions and forgery in the mock environment). To seat a row at a specific id, pre-populate `db.json` directly.

**Q: How are IDs generated?**

`crypto.randomBytes(8).toString('base64url')` — 11-character base64url strings.

**Q: What happens to my data if the server crashes mid-write?**

`lowdb` uses `steno` for atomic writes (write to temp file, then rename). A crash typically leaves either the old or new file intact, not a half-written one. You should still keep backups.

### Watching & dev mode

**Q: Do I still need `-w` / `--watch`?**

No — file watching is on by default in `1.0.0-beta.15`. The `-w` flag is preserved as a no-op for backwards compatibility and prints a yellow warning if used.

**Q: How do I disable watching?**

Set `NODE_ENV=production` in the environment before starting the server. This also enables the Eta template cache and disables `sirv` dev-mode behavior for static files.

### Missing features

**Q: Where's `--delay`?**

Not in `1.0.0-beta.15`. The community has been asking for it back ([#1545](https://github.com/typicode/json-server/issues/1545), [#1558](https://github.com/typicode/json-server/issues/1558)). For now, run a wrapper proxy or pin to the `0.x` line.

**Q: Where are custom routes (rewrites like `/api/posts → /posts`)?**

Not present in this beta. The route shape is fixed: `/<resource>` and `/<resource>/<id>`. To mount under a prefix, use a reverse proxy (Caddy, nginx, Vite's proxy, the `http-proxy-middleware` package, etc.) in front.

**Q: Where is middleware support?**

Not present in this beta. Middleware-based use cases (auth simulation, custom response shaping, per-route delays) need an external proxy or a thin wrapper that mounts `json-server` into a larger Express/Fastify app via its programmatic `createApp` export. See issue [#1617](https://github.com/typicode/json-server/issues/1617).

**Q: Where is `X-Total-Count`?**

Not present. The pagination response includes `items` (total) and `pages` in the body wrapper instead. Tracked at issue [#1727](https://github.com/typicode/json-server/issues/1727).

**Q: Can `POST` return a custom JSON shape?**

Not via configuration. The response is always the created item with its generated `id`. To customize, run `json-server` behind a wrapping server you control. Tracked at issue [#1619](https://github.com/typicode/json-server/issues/1619).

### Static files

**Q: Where do static files come from by default?**

`./public/` relative to the working directory — automatically served if it exists. Add more with `-s <dir>`. Multiple `-s` flags layer in the order they were passed.

**Q: Can I serve files from inside my JSON file?**

No — only static files on disk. Binary uploads are not supported (issue [#528](https://github.com/typicode/json-server/issues/528) is a long-standing request).

### Production

**Q: Should I deploy this to production?**

No. `json-server` is a development and prototyping tool. There is no auth, no rate limiting, no input validation beyond "is this a JSON object", and any client can read or destroy your data. See the Admin Guide's Security section for the full reasoning and mitigations.

---

_Generated April 2026 from typicode/json-server at 89a34a4. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
