From a db.json to a live API
Six steps, and only one of them differs from running json-server on your laptop — this one keeps answering after you close it.
a db.json with a posts array → GET /posts/1, POST /posts, GET /posts?_page=2&_sort=title — all derived, none of it configured
Write or upload a db.json
The box on this page already holds json-server's own example document, so you can press Create and have something to call straight away. Replace it with your own whenever you like — every top-level key becomes a resource.
Create the server
Give it a name, and an address if you want to choose one; leave the address blank and one is generated. The address is the part that appears in the URL your client calls.
Save the recovery key
It is shown once, right after the server is created, and only a hash of it is stored — so it cannot be shown again or looked up. It is how you reach this server from a browser that does not already own it, including this one after you clear cookies.
Read the routes it built
The Routes tab lists every path the document publishes, derived from the document rather than authored. If a route is on that list it answers; if a key publishes nothing, the tab names it and says why.
Point a client at the address
Copy the base address and call it from fetch, curl, an HTTP client, or a front end you have deployed. Nothing has to be running on your machine.
Watch it, and edit it
The Requests tab shows the most recent calls the server answered. The Data tab is the live document — editing it there replaces everything at once, which is the same thing a client does one record at a time — and Download db.json takes it away.
There is no password on a JSON server. Anybody with the address can read and change the document, so it is a public fixture for prototyping and never somewhere to put anything real.
What a db.json has to look like
A db.json is an object at the top level, with one key per resource. What each key holds decides what that key publishes.
An array of objects is a collection and gets the full six routes — list, create, read, replace, update and delete. A lone object is a singular resource and gets three, because there is nothing to create a second of and nothing to delete by id.
Anything else — a bare number, a string, an array of strings — publishes no routes. That is not an error: the value is still stored and still comes back from the document root, and the Routes tab names the keys it could not publish so nothing reads as having vanished.
A resource name has to survive being a path segment, so it starts with a letter or a digit and continues with letters, digits, underscores and hyphens. posts and blog_posts are resources; a key holding a slash or a question mark is stored but not published, because its own routes would be unusable.
Every record in a collection has to be an object with an id that is unique within that collection. A duplicate id is refused rather than accepted, because one of the two records could never be reached by any URL.
The routes you get
The route table is derived from the document by the same function the server matches against, so a route printed in the studio is a route that answers. These are the paths a collection named posts and a singular resource named profile publish.
| Method | Path | What it does |
|---|---|---|
| GET | /posts | Every record in the collection, filtered, sorted and paged by the query string. |
| POST | /posts | Adds a record. An id is generated when the body does not carry one. |
| GET | /posts/1 | One record by id, or 404. |
| PUT | /posts/1 | Replaces that record entirely with the body. |
| PATCH | /posts/1 | Merges the body into the record, leaving unmentioned fields alone. |
| DELETE | /posts/1 | Removes the record, and nulls foreign keys in other collections that pointed at it. |
| GET | /profile | The whole object. There is no id, so there is no record route and nothing to delete. |
| PATCH | /profile | PATCH merges into the object; PUT replaces it. |
| GET | / | The whole document, including keys that publish no routes of their own. |
Paths are the resource names exactly as written in the document, so renaming a key renames its routes. The Routes tab is the list to trust: it is generated from the same code the matcher uses, rather than being a second description that could drift from it.
A paused server answers 503 on every route until you resume it. Nothing is deleted and the document is untouched.
Filtering, sorting, paging and relations
A filter is written as field, colon, operator, equals, value. Leaving the operator out means eq, so ?views=100 and ?views:eq=100 are the same query, and a dotted field descends into nested objects — ?author.name:eq=typicode.
| Operator | Example | What it matches |
|---|---|---|
| eq | ?views=100 | The field equals the value. This is the default, so the operator can be left out. |
| ne | ?views:ne=100 | The field is anything other than the value. |
| lt | ?views:lt=100 | The field is less than the value. |
| lte | ?views:lte=100 | The field is less than or equal to the value. |
| gt | ?views:gt=100 | The field is greater than the value. |
| gte | ?views:gte=100 | The field is greater than or equal to the value. |
| in | ?id:in=1,2,3 | The field is one of a comma-separated list. |
| contains | ?title:contains=title | The field's text contains the value, ignoring case. |
| startsWith | ?title:startsWith=a | The field's text starts with the value, ignoring case. |
| endsWith | ?title:endsWith=title | The field's text ends with the value, ignoring case. |
Six query keys are read by the engine itself. Everything else in the query string is treated as a field condition.
| Key | Example | What it does |
|---|---|---|
_sort | ?_sort=views,-title | Sorts by one or more fields, comma separated, each optionally prefixed with a hyphen for descending. |
_page | ?_page=2 | Turns the reply into a paginated envelope carrying first, prev, next, last, pages, items and data. |
_per_page | ?_page=1&_per_page=25 | How many records a page holds. It only has an effect alongside _page. |
_embed | ?_embed=comments | Embeds a related resource. A plural target brings in the children pointing at this record; a singular target brings in the parent this record points at. |
_where | ?_where=… | A JSON expression for conditions the flat syntax cannot express, including and and or groups. |
_dependent | ?_dependent=comments | On a DELETE, also removes records in the named collection whose foreign key pointed at the deleted row. |
Sorting compares text the way a person reads it rather than by character code, so a title sorts before Tenth instead of after every capital letter. Ascending order also puts empty and missing values last — except zero, which sorts as the number it is.
A query value is read by its own shape, not by the type of the field it is compared against. true, false and null become those values and anything reading as a number becomes one, which has a consequence worth knowing in advance: ?code=007 asks for the number 7 and will not match a field holding the text 007. That is json-server's behaviour, and matching it is the point — a fixture that behaved differently once hosted would be the one defect this tool cannot have.
_per_page on its own is not pagination; without _page you get a plain array. When a page is asked for and no size is given, a page holds 10 records, and asking for a size of zero gives you one rather than the default.
_embed runs before filtering and sorting, which is what lets you sort on an embedded field — embedding post and then sorting on post.title works, and would not if the order were reversed.
Creating, replacing, updating and deleting
POST adds a record to a collection. PUT replaces one entirely, so fields left out of the body are gone. PATCH merges, leaving unmentioned fields alone. All three answer with the record as it now stands.
DELETE removes a record and then nulls any foreign key in other collections that pointed at it, so nothing is left referring to something that no longer exists. Add _dependent to remove those orphaned children rather than leaving them.
The Data tab in the studio edits the same document those routes write to. Saving there replaces the whole thing at once, which is worth knowing if a client is writing at the same time — the last save wins.
Reset to the original puts back the document the server was created with, and Download db.json gives you the current one as a file. It is an ordinary db.json, so json-server runs against it unchanged.
Storage, the warning, and the lock
There are two size numbers and the gap between them is deliberate: what may be uploaded is smaller than where writes stop, so a server created at its own ceiling is not full before its first POST.
| Limit | Value | Why |
|---|---|---|
| Servers | 5 per browser | An affordance rather than a wall — the cookie is yours and clearing it buys more. Creation is metered per address instead. |
| Upload | 900 KB | What may be pasted, uploaded or written back from the editor. Below the stored ceiling on purpose. |
| Stored document | 1 MB | Where growing writes stop. Reads and deletes carry on. |
| Warning | 80% | Where the usage bar changes tone and the copy starts naming the bytes left. |
| Resources | 50 per document | Top-level keys. More than this is a database rather than a fixture. |
| Records | 10,000 per collection | A bound on work rather than on size: every write rewrites the collection it touches. |
| Nesting | 24 levels | Deep enough for any realistic fixture, shallow enough to report a refusal instead of crashing on one. |
| Page size | 1,000 per request | Above this, one request could serialise the whole document many times over. |
| Request log | 50 per server | Newest first; the oldest are trimmed after each write. |
At the ceiling, POST, PUT and PATCH answer 507 while GET, DELETE and OPTIONS carry on. That is what makes the lock recoverable: a full server can still be read, and deleting records you no longer need is the way out. A limit that also refused DELETE would be a trap whose only escape was discarding everything.
The usage bar changes tone well before that point and names how many bytes are left, so the lock is something you saw coming rather than something that happened to you.
Where this differs from a local json-server
The routes, the query language and the shapes are json-server v1's, and they were checked by running both engines over the same document rather than by reading the documentation — which found seven behaviours a hand-written implementation had got wrong. A well-formed query behaves here exactly as it does locally.
What differs is where it runs. There is no process on your machine to keep alive, so a preview deployment, a phone on another network or a colleague can all reach the same fixture. There is also no localhost to hide behind: anybody with the address has full read and write access.
Malformed input is refused rather than ignored. A _where that is not valid JSON and an unknown operator both answer 400 here, where the reference silently drops the filter and returns the whole collection. A filter that is quietly ignored looks exactly like one that matched everything, which is the worse failure.
One _where clause is honoured here that the reference matches nothing for, and that is strictly additive. None of these three differences can change what a correct client sees.
Where the reference has a defect, it is matched rather than fixed — a nested _where clause against a field that is not an object passes there and passes here. Imposing a judgement about which behaviour is nicer is exactly what would make a hosted fixture disagree with a local one.
