99

Concurrent visitors over the last 30 minutes. (~)

Peak concurrent visitors -
Yesterday: 0 / Today: 0

JSON Server Essentials

JSON Server Essentials

This post is based on JSON Server 1.0.0-beta.2.

# What is JSON Server?

JSON Server is a tool that lets you build a mock RESTful API server and DB with a JSON file.
With it, you can develop or test prototypes even when the backend API server or DB is not ready yet.

# Installing and running

Install the json-server package.
Since the mock API server has to run separately from the frontend development server, it is also a good idea to install the concurrently package, which can run scripts at the same time.

BASH
1
npm i -D json-server concurrently

Once the packages are installed, add scripts to package.json as follows.
With an npm run command, you can run all scripts that start with the watch: prefix at the same time.

The default port of a Vite project is 5173, and the default port of JSON Server is 3000.
You can change the JSON Server port with the --port flag.

/package.json
JSON
1
2
3
4
5
6
7
{ "scripts": { "watch:json-server": "json-server db.json --port 3000", "watch:vite": "vite", "dev": "concurrently npm:watch:*" } }

Now you can run the frontend development server and the mock API server at the same time with the npm run dev command.

BASH
1
npm run dev

# Creating the JSON file

Once the package installation is complete, create a db.json file that acts as the database.
Then add the collections you want to manage by hand, as follows.

/db.json
JSON
1
2
3
4
5
{ "users": [], "posts": [], "comments": [] }

If you think of the JSON file in terms of the structure of MongoDB, a well-known NoSQL database, each top-level array is a collection, each object item in an array is a document, and each property of an object item is a field.

JSON
1
2
3
4
5
6
7
8
9
10
{ "collection": [ document { "field": "value", "field": "value" }, document, document ] }

First, for practice, you can write JSON data (db.json) like this.
The data has 5 users, 2 posts, and 3 comments, and each post and comment refers to the user who wrote it with userId.
Each comment also refers to the post it belongs to with postId.

/db.json
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{ "users": [ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "7a8b", "name": "Lewis", "age": 57 }, { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "2b1c", "name": "Alex", "age": 22 }, { "id": "4e6a", "name": "Mike", "age": 41 } ], "posts": [ { "id": "47g2", "createdAt": "2025-12-16", "title": "Comparing CSS Grid and Flexbox", "content": "How to decide between CSS Grid and Flex when building a layout. Compares their pros and cons.", "userId": "7a8b" }, { "id": "59h3", "createdAt": "2024-09-29", "title": "Tips for Improving JavaScript Performance", "content": "Introduces JavaScript optimization techniques that improve the performance of web applications.", "userId": "58f2" } ], "comments": [ { "id": "91bg", "createdAt": "2026-02-07", "comment": "Grid and Flex were always hard for me. Thanks for the clear explanation!", "userId": "2b1c", "postId": "47g2" }, { "id": "92ch", "createdAt": "2024-10-01", "comment": "These performance tips are really useful. My app got much faster!", "userId": "2b1c", "postId": "59h3" }, { "id": "93di", "createdAt": "2025-03-14", "comment": "Thanks to this explanation, I think I can manage it well.", "userId": "4e6a", "postId": "59h3" } ] }
Practice data

If you define the schema of the data above in TypeScript, it looks like this.
The user and post properties are filled in when you use the _embed query parameter.
You can find more about this in 'Fetching data / References'.

/server.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export interface User { id: string name: string age: number } export interface Post { id: string createdAt: string title: string content: string userId: string user?: User } export interface Comment { id: string createdAt: string comment: string userId: string postId: string user?: User post?: Post }

# Making requests

Following RESTful principles, JSON Server handles requests per collection.
You write the collection name in the request URL and choose how the request is handled by specifying the request method.

cURL
1
2
curl http://localhost:3000/collection \ -X METHOD
cURL
1
2
curl http://localhost:3000/users \ -X GET \
Example of fetching the user list
cURL
1
2
3
curl http://localhost:3000/users/9c3d \ -X PATCH \ -d '{ "age": 99 }'
Example of updating a user's information by ID

You can use the following request methods.

  • GET: Fetch data
  • POST: Create data
  • PUT: Update all of the data
  • PATCH: Update part of the data
  • DELETE: Delete data

# Fetching data

To fetch the user list from the DB, you can write a function like this.

If you add conditions with a query string, you can filter and fetch only the data that matches them.
Without a query string, you fetch all the data in the collection.

TS
1
2
3
4
5
export async function fetchUsers(query = ''): Promise<User[]> { const res = await fetch(`http://localhost:3000/users/?${query}`) // GET const users = await res.json() return users }
Function that fetches the user list
TS
1
fetchUsers()
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "7a8b", "name": "Lewis", "age": 57 }, { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "2b1c", "name": "Alex", "age": 22 }, { "id": "4e6a", "name": "Mike", "age": 41 } ]
Response

# Conditions

You can filter data by adding conditions with a query string.
A name that starts with the _ symbol is treated as a query parameter, not a regular field.

1
field_condition=value
  • _lt: Less Than (<)
  • _lte: Less Than or Equal (<=)
  • _gt: Greater Than (>)
  • _gte: Greater Than or Equal (>=)
  • _ne: Not Equal (!=)

The following example fetches users whose age field (age) is 22, that is, users who are 22 years old.

TS
1
fetchUsers('age=22')
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
[ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "2b1c", "name": "Alex", "age": 22 } ]
Response

The following example fetches users younger than 41.

TS
1
fetchUsers('age_lt=41')
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "2b1c", "name": "Alex", "age": 22 } ]
Response

The following example fetches users aged 41 or younger.

TS
1
fetchUsers('age_lte=41')
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "2b1c", "name": "Alex", "age": 22 }, { "id": "4e6a", "name": "Mike", "age": 41 } ]
Response

You can also add other conditions like this.

TS
1
2
3
fetchUsers('age_gt=41') // Users older than 41 fetchUsers('age_gte=41') // Users aged 41 or older fetchUsers('age_ne=41') // Users who are not 41
Function call

# Ranges

You can fetch data by specifying a range with a query string.
You can specify which items to fetch, from where to where, or how many.

  • _start: Specifies the start index to fetch from.
  • _end: Specifies the end index to fetch up to.
  • _limit: Specifies how many items to fetch.

The following examples all return the same result.
Like .slice() on arrays, the _end parameter fetches up to, but not including, the end index.

TS
1
2
3
fetchUsers('_start=0&_end=2') // From index 0 up to, but not including, index 2 fetchUsers('_start=0&_limit=2') // 2 items starting from index 0 fetchUsers('_limit=2') // 2 items starting from index 0
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
[ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "7a8b", "name": "Lewis", "age": 57 } ]
Response

# Pagination

You can split the data into pages and fetch it one page at a time.

  • _page: Specifies the page to fetch.
  • _per_page: Specifies the number of items per page. The default value is 10.
TS
1
fetchUsers('_page=2&_per_page=2')
Function call

If you check the response, it includes the following page information.
The actual data is in the data property.

  • first: First page number
  • prev: Previous page number
  • next: Next page number
  • last: Last page number
  • pages: Total number of pages
  • items: Total number of items
  • data: Response data
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{ "first": 1, "prev": 1, "next": 3, "last": 3, "pages": 3, "items": 5, "data": [ { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "2b1c", "name": "Alex", "age": 22 } ] }
Response

# Sorting

You can specify which field to sort by.
By default, data is sorted in ascending order (asc).

  • _sort: Specifies the field to sort by.
TS
1
fetchUsers('_sort=age')
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "2b1c", "name": "Alex", "age": 22 }, { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "4e6a", "name": "Mike", "age": 41 }, { "id": "7a8b", "name": "Lewis", "age": 57 } ]
Response

If you want to sort the fetched data in descending order (desc), add the - symbol in front of the field.

TS
1
fetchUsers('_sort=-age')
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[ { "id": "7a8b", "name": "Lewis", "age": 57 }, { "id": "4e6a", "name": "Mike", "age": 41 }, { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "2b1c", "name": "Alex", "age": 22 } ]
Response

You can also sort by multiple fields.
The following sorts by age in descending order, and when the ages are the same, sorts by name in ascending order.

TS
1
fetchUsers('_sort=-age,name')
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[ { "id": "7a8b", "name": "Lewis", "age": 57 }, { "id": "4e6a", "name": "Mike", "age": 41 }, { "id": "9c3d", "name": "Evan", "age": 35 }, { "id": "2b1c", "name": "Alex", "age": 22 }, { "id": "58f2", "name": "Neo", "age": 22 } ]
Response

# References

This time, let's write a function that fetches the list of comments.
As before, it lets you filter the data with a query string.

TS
1
2
3
4
5
export async function fetchComments(query = ''): Promise<User[]> { const res = await fetch(`http://localhost:3000/comments/?${query}`) // GET const comments = await res.json() return comments }
Function that fetches the comment list

If you don't pass a query string, you fetch all comments.

TS
1
fetchComments()
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[ { "id": "91bg", "createdAt": "2026-02-07", "comment": "Grid and Flex were always hard for me. Thanks for the clear explanation!", "userId": "2b1c", "postId": "47g2" }, { "id": "92ch", "createdAt": "2024-10-01", "comment": "These performance tips are really useful. My app got much faster!", "userId": "2b1c", "postId": "59h3" }, { "id": "93di", "createdAt": "2025-03-14", "comment": "Thanks to this explanation, I think I can manage it well.", "userId": "4e6a", "postId": "59h3" } ]
Response

With the _embed query parameter, you can fetch the comment list together with each related post, using the post ID (postId).

Note that the value of the _embed query parameter is post, not posts!

TS
1
fetchComments('_embed=post')
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[ { "id": "91bg", "createdAt": "2026-02-07", "comment": "Grid and Flex were always hard for me. Thanks for the clear explanation!", "userId": "2b1c", "postId": "47g2", "post": { "id": "47g2", "createdAt": "2025-12-16", "title": "Comparing CSS Grid and Flexbox", "content": "How to decide between CSS Grid and Flex when building a layout. Compares their pros and cons.", "userId": "7a8b" } }, { "id": "92ch", "createdAt": "2024-10-01", "comment": "These performance tips are really useful. My app got much faster!", "userId": "2b1c", "postId": "59h3", "post": { "id": "59h3", "createdAt": "2024-09-29", "title": "Tips for Improving JavaScript Performance", "content": "Introduces JavaScript optimization techniques that improve the performance of web applications.", "userId": "58f2" } }, { "id": "93di", "createdAt": "2025-03-14", "comment": "Thanks to this explanation, I think I can manage it well.", "userId": "4e6a", "postId": "59h3", "post": { "id": "59h3", "createdAt": "2024-09-29", "title": "Tips for Improving JavaScript Performance", "content": "Introduces JavaScript optimization techniques that improve the performance of web applications.", "userId": "58f2" } } ]
Response

You can also write the _embed query parameter several times to fetch multiple referenced data at once.

TS
1
fetchComments('_embed=post&_embed=user')
Function call
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[ { "id": "91bg", "createdAt": "2026-02-07", "comment": "Grid and Flex were always hard for me. Thanks for the clear explanation!", "userId": "2b1c", "postId": "47g2", "post": { "id": "47g2", "createdAt": "2025-12-16", "title": "Comparing CSS Grid and Flexbox", "content": "How to decide between CSS Grid and Flex when building a layout. Compares their pros and cons.", "userId": "7a8b" }, "user": { "id": "2b1c", "name": "Alex", "age": 22 } }, { "id": "92ch", "createdAt": "2024-10-01", "comment": "These performance tips are really useful. My app got much faster!", "userId": "2b1c", "postId": "59h3", "post": { "id": "59h3", "createdAt": "2024-09-29", "title": "Tips for Improving JavaScript Performance", "content": "Introduces JavaScript optimization techniques that improve the performance of web applications.", "userId": "58f2" }, "user": { "id": "2b1c", "name": "Alex", "age": 22 } }, { "id": "93di", "createdAt": "2025-03-14", "comment": "Thanks to this explanation, I think I can manage it well.", "userId": "4e6a", "postId": "59h3", "post": { "id": "59h3", "createdAt": "2024-09-29", "title": "Tips for Improving JavaScript Performance", "content": "Introduces JavaScript optimization techniques that improve the performance of web applications.", "userId": "58f2" }, "user": { "id": "4e6a", "name": "Mike", "age": 41 } } ]
Response

# Adding data

You can add data with a POST request.
The id field of data created by a POST request is generated automatically by JSON Server, so leave the id field out.

TypeScript's Omit utility type returns a new type without the specified properties.

TS
1
2
3
4
5
6
7
8
export async function createUser(user: Omit<User, 'id'>): Promise<User> { const res = await fetch('http://localhost:3000/users', { method: 'POST', body: JSON.stringify(user) }) const createdUser = await res.json() return createdUser }
Function that creates user data
TS
1
2
3
4
createUser({ name: 'John', age: 30 })
Function call

The response returns the created document.
You can see that it includes the id field.

JSON
1
2
3
4
5
{ "id": "1a2b", "name": "John", "age": 30 }
Response

# Updating data

You can update data with a PUT or PATCH request.

# Full update

A PUT request asks to update the data as a whole.
So you also have to send the fields you are not changing.
You can write a function like the following, which includes the ID of the data to update in the request URL and puts the data to update in the request body.

A PUT request can update the data as a whole, but it cannot change the id field.

TS
1
2
3
4
5
6
7
8
export async function updateUser(user: User): Promise<User> { const res = await fetch(`http://localhost:3000/users/${user.id}`, { method: 'PUT', body: JSON.stringify(user) }) const updatedUser = await res.json() return updatedUser }
Function that updates all of the user data
TS
1
2
3
4
5
updateUser({ id: '58f2', name: 'Neo', age: 123 // Changed })
Function call

It returns the updated data.

JSON
1
2
3
4
5
{ "id": "58f2", "name": "Neo", "age": 123 }
Response

# Partial update

A PATCH request asks to update part of the data.
So you don't need to send the fields you are not changing.

Partial<User> & Pick<User, 'id'> is a type based on User where the id property is required and all the other properties are optional.


///

TS
1
2
3
4
5
6
7
8
export async function updateUser(user: Partial<User> & Pick<User, 'id'>): Promise<User> { const res = await fetch(`http://localhost:3000/users/${user.id}`, { method: 'PATCH', body: JSON.stringify(user) }) const updatedUser = await res.json() return updatedUser }
Function that updates part of the user data
TS
1
2
3
4
updateUser({ id: '58f2', age: 123 // Changed })
Function call
JSON
1
2
3
4
5
{ "id": "58f2", "name": "Neo", "age": 123 }
Response

# Deleting data

You can delete specific data with a DELETE request.
You must include the ID of the data to delete in the request URL.

TS
1
2
3
4
5
6
7
export async function deleteUser(userId: string): Promise<User> { const res = await fetch(`http://localhost:3000/users/${userId}`, { method: 'DELETE' }) const user = await res.json() return user }
Function that deletes user data
TS
1
deleteUser('4e6a')

It returns the deleted data.

JSON
1
2
3
4
5
{ "id": "4e6a", "name": "Mike", "age": 41 }
Response

# Bulk delete

To delete multiple items with a single request, you can write a function like this.

TS
1
2
3
4
5
6
7
8
9
export async function deleteUsers(userIds: string[] = []): Promise<User[]> { return Promise.all(userIds.map(async userId => { const res = await fetch(`http://localhost:3000/users/${userId}`, { method: 'DELETE' }) const deletedUser = await res.json() return deletedUser })) }
Function that deletes multiple users at once

Pass the IDs of the data to delete as an array.

TS
1
deleteUsers(['58f2', '7a8b', '9c3d'])

It returns the list of deleted data.

JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ { "id": "58f2", "name": "Neo", "age": 22 }, { "id": "7a8b", "name": "Lewis", "age": 57 }, { "id": "9c3d", "name": "Evan", "age": 35 } ]
Response

When you delete data with a DELETE request, you can also delete other data that refers to it.
Just use the _dependent query parameter.

The following function can delete a post together with its related comments.
It deletes all comments in the comments collection whose postId field matches the ID of the deleted post.

TS
1
2
3
4
5
6
7
8
export async function deletePost(postId: string, isDeleteRefs = true): Promise<Post> { const query = isDeleteRefs ? '_dependent=comments' : '' const res = await fetch(`http://localhost:3000/posts/${postId}?${query}`, { method: 'DELETE' }) const deletedPost = await res.json() return deletedPost }
Function that deletes post data

The default value of the isDeleteRefs argument is true, so you can omit it when calling the function.
Then both the post and its related comments are deleted.

TS
1
2
3
// Deletes the post along with its related comments. deletePost('59h3') deletePost('59h3', true)
Function call

If you want to delete a post but keep its related comments, pass false as the second argument, as follows.

TS
1
2
// Deletes the post but keeps its related comments. deletePost('59h3', false)

Then, because the post document they referred to is gone, null is assigned to the postId field.

JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{ "comments": [ { "id": "91bg", "createdAt": "2026-02-07", "comment": "Grid and Flex were always hard for me. Thanks for the clear explanation!", "userId": "2b1c", "postId": "47g2" }, { "id": "92ch", "createdAt": "2024-10-01", "comment": "These performance tips are really useful. My app got much faster!", "userId": "2b1c", "postId": null }, { "id": "93di", "createdAt": "2025-03-14", "comment": "Thanks to this explanation, I think I can manage it well.", "userId": "4e6a", "postId": null } ] }
Resulting comments collection