json-server 1.0.0-beta.1 → 1.0.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,44 +1,21 @@
1
- Fair Source License, version 0.9
2
-
3
- Copyright (C) 2023-present typicode
4
-
5
- Licensor: typicode
6
-
7
- Software: json-server
8
-
9
- Use Limitation: 2 users
10
-
11
- License Grant. Licensor hereby grants to each recipient of the
12
- Software ("you") a non-exclusive, non-transferable, royalty-free and
13
- fully-paid-up license, under all of the Licensor's copyright and
14
- patent rights, to use, copy, distribute, prepare derivative works of,
15
- publicly perform and display the Software, subject to the Use
16
- Limitation and the conditions set forth below.
17
-
18
- Use Limitation. The license granted above allows use by up to the
19
- number of users per entity set forth above (the "Use Limitation"). For
20
- determining the number of users, "you" includes all affiliates,
21
- meaning legal entities controlling, controlled by, or under common
22
- control with you. If you exceed the Use Limitation, your use is
23
- subject to payment of Licensor's then-current list price for licenses.
24
-
25
- Conditions. Redistribution in source code or other forms must include
26
- a copy of this license document to be provided in a reasonable
27
- manner. Any redistribution of the Software is only allowed subject to
28
- this license.
29
-
30
- Trademarks. This license does not grant you any right in the
31
- trademarks, service marks, brand names or logos of Licensor.
32
-
33
- DISCLAIMER. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OR
34
- CONDITION, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES
35
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36
- NONINFRINGEMENT. LICENSORS HEREBY DISCLAIM ALL LIABILITY, WHETHER IN
37
- AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
38
- CONNECTION WITH THE SOFTWARE.
39
-
40
- Termination. If you violate the terms of this license, your rights
41
- will terminate automatically and will not be reinstated without the
42
- prior written consent of Licensor. Any such termination will not
43
- affect the right of others who may have received copies of the
44
- Software from you.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 typicode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
- # json-server
1
+ # JSON-Server
2
2
 
3
3
  [![Node.js CI](https://github.com/typicode/json-server/actions/workflows/node.js.yml/badge.svg)](https://github.com/typicode/json-server/actions/workflows/node.js.yml)
4
4
 
5
5
  > [!IMPORTANT]
6
- > Viewing beta v1 documentation – usable but expect breaking changes. For stable version, see [here](https://github.com/typicode/json-server/tree/v0)
6
+ > Viewing beta v1 documentation – usable but expect breaking changes. For stable version, see [here](https://github.com/typicode/json-server/tree/v0.17.4)
7
7
 
8
- 👋 _Hey! Using React, Vue or Astro? Check my new project [MistCSS](https://github.com/typicode/mistcss) to write 50% less code._
8
+ > [!NOTE]
9
+ > Using React ⚛️ and tired of CSS-in-JS? See [MistCSS](https://github.com/typicode/mistcss) 👀
9
10
 
10
11
  ## Install
11
12
 
@@ -19,6 +20,7 @@ Create a `db.json` or `db.json5` file
19
20
 
20
21
  ```json
21
22
  {
23
+ "$schema": "./node_modules/json-server/schema.json",
22
24
  "posts": [
23
25
  { "id": "1", "title": "a title", "views": 100 },
24
26
  { "id": "2", "title": "another title", "views": 200 }
@@ -40,15 +42,15 @@ Create a `db.json` or `db.json5` file
40
42
  ```json5
41
43
  {
42
44
  posts: [
43
- { id: '1', title: 'a title', views: 100 },
44
- { id: '2', title: 'another title', views: 200 },
45
+ { id: "1", title: "a title", views: 100 },
46
+ { id: "2", title: "another title", views: 200 },
45
47
  ],
46
48
  comments: [
47
- { id: '1', text: 'a comment about post 1', postId: '1' },
48
- { id: '2', text: 'another comment about post 1', postId: '1' },
49
+ { id: "1", text: "a comment about post 1", postId: "1" },
50
+ { id: "2", text: "another comment about post 1", postId: "1" },
49
51
  ],
50
52
  profile: {
51
- name: 'typicode',
53
+ name: "typicode",
52
54
  },
53
55
  }
54
56
  ```
@@ -57,19 +59,30 @@ You can read more about JSON5 format [here](https://github.com/json5/json5).
57
59
 
58
60
  </details>
59
61
 
60
- Pass it to JSON Server CLI
62
+ Start JSON Server
61
63
 
62
- ```shell
63
- $ npx json-server db.json
64
+ ```bash
65
+ npx json-server db.json
66
+ ```
67
+
68
+ This starts the server at `http://localhost:3000`. You should see:
69
+ ```
70
+ JSON Server started on PORT :3000
71
+ http://localhost:3000
64
72
  ```
65
73
 
66
- Get a REST API
74
+ Access your REST API:
67
75
 
68
- ```shell
69
- $ curl http://localhost:3000/posts/1
76
+ ```bash
77
+ curl http://localhost:3000/posts/1
78
+ ```
79
+
80
+ **Response:**
81
+ ```json
70
82
  {
71
83
  "id": "1",
72
- "title": "a title"
84
+ "title": "a title",
85
+ "views": 100
73
86
  }
74
87
  ```
75
88
 
@@ -77,135 +90,174 @@ Run `json-server --help` for a list of options
77
90
 
78
91
  ## Sponsors ✨
79
92
 
80
- | Sponsors |
81
- | :---: |
82
- | <a href="https://mockend.com/" target="_blank"><img src="https://jsonplaceholder.typicode.com/mockend.svg" height="100px"></a> |
83
- | <a href="https://zuplo.link/json-server-gh"><picture><source media="(prefers-color-scheme: dark)" srcset="https://github.com/typicode/json-server/assets/5502029/bb7abea9-fc54-4612-b7ab-0221c60b8ac7"><img alt="scalar" src="https://github.com/typicode/json-server/assets/5502029/51f5afed-7ba0-41bf-b9f2-ef9d8ab9dff7"></picture></a> |
93
+ ### Gold
84
94
 
95
+ | |
96
+ | :--------------------------------------------------------------------------------------------------------------------------------------------------------: |
97
+ | <a href="https://mockend.com/" target="_blank"><img src="https://jsonplaceholder.typicode.com/mockend.svg" height="100px"></a> |
98
+ | <a href="https://zuplo.link/json-server-gh"><img src="https://github.com/user-attachments/assets/adfee31f-a8b6-4684-9a9b-af4f03ac5b75" height="100px"></a> |
99
+ | <a href="https://www.mintlify.com/"><img src="https://github.com/user-attachments/assets/bcc8cc48-b2d9-4577-8939-1eb4196b7cc5" height="100px"></a> |
100
+ | <a href="http://git-tower.com/?utm_source=husky&utm_medium=referral"><img height="100px" alt="tower-dock-icon-light" src="https://github.com/user-attachments/assets/b6b4ab20-beff-4e5c-9845-bb9d60057196" /></a> |
101
+ | <a href="https://serpapi.com/?utm_source=typicode"><img height="100px" src="https://github.com/user-attachments/assets/52b3039d-1e4c-4c68-951c-93f0f1e73611" /></a>
85
102
 
86
- | Sponsors |
87
- | :---: |
88
- | <a href="https://konghq.com/products/kong-konnect/register?utm_medium=referral&utm_source=github&utm_campaign=platform&utm_content=json-server"><img src="https://github.com/typicode/json-server/assets/5502029/e8d8ecb2-3c45-4f60-92d0-a060b820fa7f" height="75px"></a> |
89
103
 
90
- | Sponsors | |
91
- | :---: | :---: |
92
- | <a href="https://www.storyblok.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/c6b10674-4ada-4616-91b8-59d30046b45a" height="35px"></a> | <a href="https://betterstack.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/44679f8f-9671-470d-b77e-26d90b90cbdc" height="35px"></a> |
93
- | <a href="https://www.globalsoftwarecompanies.com" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/d0bb2e9b-9b74-4d91-9c9e-f3c87e152918" height="35px"></a> | <a href="https://beeceptor.com/?utm_source=json-server"><img src="https://github.com/typicode/json-server/assets/5502029/57b852a6-60b9-426b-986e-a148e82783c2" height="35px"></a> |
104
+ ### Silver
94
105
 
106
+ | |
107
+ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
108
+ | <a href="https://requestly.com?utm_source=githubsponsor&utm_medium=jsonserver&utm_campaign=jsonserver"><img src="https://github.com/user-attachments/assets/f7e7b3cf-97e2-46b8-81c8-cb3992662a1c" style="height:70px; width:auto;"></a> |
95
109
 
110
+ ### Bronze
96
111
 
112
+ | | |
113
+ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
114
+ | <a href="https://www.storyblok.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/c6b10674-4ada-4616-91b8-59d30046b45a" height="35px"></a> | <a href="https://betterstack.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/44679f8f-9671-470d-b77e-26d90b90cbdc" height="35px"></a> |
97
115
 
98
116
  [Become a sponsor and have your company logo here](https://github.com/users/typicode/sponsorship)
99
117
 
100
- ## Sponsorware
118
+ ## Query Capabilities
101
119
 
102
- > [!NOTE]
103
- > This project uses the [Fair Source License](https://fair.io/). Only organizations with 3+ users are kindly asked to contribute a small amount through sponsorship [sponsor](https://github.com/sponsors/typicode) for usage. __This license helps keep the project sustainable and healthy, benefiting everyone.__
104
- >
105
- > For more information, FAQs, and the rationale behind this, visit [https://fair.io/](https://fair.io/).
120
+ JSON Server supports advanced querying out of the box:
121
+
122
+ ```http
123
+ GET /posts?views:gt=100 # Filter by condition
124
+ GET /posts?_sort=-views # Sort by field (descending)
125
+ GET /posts?_page=1&_per_page=10 # Pagination
126
+ GET /posts?_embed=comments # Include relations
127
+ GET /posts?_where={"or":[...]} # Complex queries
128
+ ```
129
+
130
+ See detailed documentation below for each feature.
106
131
 
107
132
  ## Routes
108
133
 
109
- Based on the example `db.json`, you'll get the following routes:
134
+ ### Array Resources
110
135
 
111
- ```
136
+ For array resources like `posts` and `comments`:
137
+
138
+ ```http
112
139
  GET /posts
113
140
  GET /posts/:id
114
141
  POST /posts
115
142
  PUT /posts/:id
116
143
  PATCH /posts/:id
117
144
  DELETE /posts/:id
118
-
119
- # Same for comments
120
145
  ```
121
146
 
122
- ```
147
+ ### Object Resources
148
+
149
+ For singular object resources like `profile`:
150
+
151
+ ```http
123
152
  GET /profile
124
153
  PUT /profile
125
154
  PATCH /profile
126
155
  ```
127
156
 
128
- ## Params
157
+ ## Query params
129
158
 
130
159
  ### Conditions
131
160
 
132
- - ` ` → `==`
133
- - `lt` → `<`
134
- - `lte` → `<=`
135
- - `gt` → `>`
136
- - `gte` → `>=`
137
- - `ne` `!=`
138
-
139
- ```
140
- GET /posts?views_gt=9000
161
+ Use `field:operator=value`.
162
+
163
+ Operators:
164
+
165
+ - no operator -> `eq` (equal)
166
+ - `lt` less than, `lte` less than or equal
167
+ - `gt` greater than, `gte` greater than or equal
168
+ - `eq` equal, `ne` not equal
169
+ - `in` included in comma-separated list
170
+ - `contains` string contains (case-insensitive)
171
+ - `startsWith` string starts with (case-insensitive)
172
+ - `endsWith` string ends with (case-insensitive)
173
+
174
+ Examples:
175
+
176
+ ```http
177
+ GET /posts?views:gt=100
178
+ GET /posts?title:eq=Hello
179
+ GET /posts?id:in=1,2,3
180
+ GET /posts?author.name:eq=typicode
181
+ GET /posts?title:contains=hello
182
+ GET /posts?title:startsWith=Hello
183
+ GET /posts?title:endsWith=world
141
184
  ```
142
185
 
143
- ### Range
144
-
145
- - `start`
146
- - `end`
147
- - `limit`
186
+ ### Sort
148
187
 
149
- ```
150
- GET /posts?_start=10&_end=20
151
- GET /posts?_start=10&_limit=10
188
+ ```http
189
+ GET /posts?_sort=title
190
+ GET /posts?_sort=-views
191
+ GET /posts?_sort=author.name,-views
152
192
  ```
153
193
 
154
- ### Paginate
194
+ ### Pagination
155
195
 
156
- - `page`
157
- - `per_page` (default = 10)
158
-
159
- ```
196
+ ```http
160
197
  GET /posts?_page=1&_per_page=25
161
198
  ```
162
199
 
163
- ### Sort
164
-
165
- - `_sort=f1,f2`
166
-
167
- ```
168
- GET /posts?_sort=id,-views
200
+ **Response:**
201
+ ```json
202
+ {
203
+ "first": 1,
204
+ "prev": null,
205
+ "next": 2,
206
+ "last": 4,
207
+ "pages": 4,
208
+ "items": 100,
209
+ "data": [
210
+ { "id": "1", "title": "...", "views": 100 },
211
+ { "id": "2", "title": "...", "views": 200 }
212
+ ]
213
+ }
169
214
  ```
170
215
 
171
- ### Nested and array fields
172
-
173
- - `x.y.z...`
174
- - `x.y.z[i]...`
175
-
176
- ```
177
- GET /foo?a.b=bar
178
- GET /foo?x.y_lt=100
179
- GET /foo?arr[0]=bar
180
- ```
216
+ **Notes:**
217
+ - `_per_page` defaults to `10` if not specified
218
+ - Invalid `_page` or `_per_page` values are automatically normalized to valid ranges
181
219
 
182
220
  ### Embed
183
221
 
184
- ```
222
+ ```http
185
223
  GET /posts?_embed=comments
186
224
  GET /comments?_embed=post
187
225
  ```
188
226
 
189
- ## Delete
227
+ ### Complex filter with `_where`
228
+
229
+ `_where` accepts a JSON object and overrides normal query params when valid.
190
230
 
231
+ ```http
232
+ GET /posts?_where={"or":[{"views":{"gt":100}},{"author":{"name":{"lt":"m"}}}]}
191
233
  ```
192
- DELETE /posts/1
234
+
235
+ ## Delete dependents
236
+
237
+ ```http
193
238
  DELETE /posts/1?_dependent=comments
194
239
  ```
195
240
 
196
- ## Serving static files
241
+ ## Static Files
197
242
 
198
- If you create a `./public` directory, JSON Serve will serve its content in addition to the REST API.
243
+ JSON Server automatically serves files from the `./public` directory.
199
244
 
200
- You can also add custom directories using `-s/--static` option.
245
+ To serve additional static directories:
201
246
 
202
- ```sh
203
- json-server -s ./static
204
- json-server -s ./static -s ./node_modules
247
+ ```bash
248
+ json-server db.json -s ./static
249
+ json-server db.json -s ./static -s ./node_modules
205
250
  ```
206
251
 
207
- ## Notable differences with v0.17
252
+ Static files are served with standard MIME types and can include HTML, CSS, JavaScript, images, and other assets.
253
+
254
+ ## Migration Notes (v0 → v1)
255
+
256
+ If you are upgrading from json-server v0.x, note these behavioral changes:
257
+
258
+ - **ID handling:** `id` is always a string and will be auto-generated if not provided
259
+ - **Pagination:** Use `_per_page` with `_page` instead of the deprecated `_limit` parameter
260
+ - **Relationships:** Use `_embed` instead of `_expand` for including related resources
261
+ - **Request delays:** Use browser DevTools (Network tab > throttling) instead of the removed `--delay` CLI option
208
262
 
209
- - `id` is always a string and will be generated for you if missing
210
- - use `_per_page` with `_page` instead of `_limit`for pagination
211
- - use Chrome's `Network tab > throtling` to delay requests instead of `--delay` CLI option
263
+ > **New to json-server?** These notes are for users migrating from v0. If this is your first time using json-server, you can ignore this section.
@@ -0,0 +1,31 @@
1
+ import { randomId } from "../random-id.js";
2
+ export const DEFAULT_SCHEMA_PATH = './node_modules/json-server/schema.json';
3
+ export class NormalizedAdapter {
4
+ #adapter;
5
+ constructor(adapter) {
6
+ this.#adapter = adapter;
7
+ }
8
+ async read() {
9
+ const data = await this.#adapter.read();
10
+ if (data === null) {
11
+ return null;
12
+ }
13
+ delete data['$schema'];
14
+ for (const value of Object.values(data)) {
15
+ if (Array.isArray(value)) {
16
+ for (const item of value) {
17
+ if (typeof item['id'] === 'number') {
18
+ item['id'] = item['id'].toString();
19
+ }
20
+ if (item['id'] === undefined) {
21
+ item['id'] = randomId();
22
+ }
23
+ }
24
+ }
25
+ }
26
+ return data;
27
+ }
28
+ async write(data) {
29
+ await this.#adapter.write({ ...data, $schema: DEFAULT_SCHEMA_PATH });
30
+ }
31
+ }
package/lib/app.js CHANGED
@@ -3,15 +3,70 @@ import { fileURLToPath } from 'node:url';
3
3
  import { App } from '@tinyhttp/app';
4
4
  import { cors } from '@tinyhttp/cors';
5
5
  import { Eta } from 'eta';
6
+ import { Low } from 'lowdb';
6
7
  import { json } from 'milliparsec';
7
8
  import sirv from 'sirv';
8
- import { isItem, Service } from './service.js';
9
+ import { parseWhere } from "./parse-where.js";
10
+ import { isItem, Service } from "./service.js";
9
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
12
  const isProduction = process.env['NODE_ENV'] === 'production';
11
13
  const eta = new Eta({
12
14
  views: join(__dirname, '../views'),
13
15
  cache: isProduction,
14
16
  });
17
+ const RESERVED_QUERY_KEYS = new Set(['_sort', '_page', '_per_page', '_embed', '_where']);
18
+ function parseListParams(req) {
19
+ const queryString = req.url.split('?')[1] ?? '';
20
+ const params = new URLSearchParams(queryString);
21
+ const filterParams = new URLSearchParams();
22
+ for (const [key, value] of params.entries()) {
23
+ if (!RESERVED_QUERY_KEYS.has(key)) {
24
+ filterParams.append(key, value);
25
+ }
26
+ }
27
+ let where = parseWhere(filterParams.toString());
28
+ const rawWhere = params.get('_where');
29
+ if (typeof rawWhere === 'string') {
30
+ try {
31
+ const parsed = JSON.parse(rawWhere);
32
+ if (typeof parsed === 'object' && parsed !== null) {
33
+ where = parsed;
34
+ }
35
+ }
36
+ catch {
37
+ // Ignore invalid JSON and fallback to parsed query params
38
+ }
39
+ }
40
+ const pageRaw = params.get('_page');
41
+ const perPageRaw = params.get('_per_page');
42
+ const page = pageRaw === null ? undefined : Number.parseInt(pageRaw, 10);
43
+ const perPage = perPageRaw === null ? undefined : Number.parseInt(perPageRaw, 10);
44
+ return {
45
+ where,
46
+ sort: params.get('_sort') ?? undefined,
47
+ page: Number.isNaN(page) ? undefined : page,
48
+ perPage: Number.isNaN(perPage) ? undefined : perPage,
49
+ embed: req.query['_embed'],
50
+ };
51
+ }
52
+ function withBody(action) {
53
+ return async (req, res, next) => {
54
+ const { name = '' } = req.params;
55
+ if (isItem(req.body)) {
56
+ res.locals['data'] = await action(name, req.body);
57
+ }
58
+ next?.();
59
+ };
60
+ }
61
+ function withIdAndBody(action) {
62
+ return async (req, res, next) => {
63
+ const { name = '', id = '' } = req.params;
64
+ if (isItem(req.body)) {
65
+ res.locals['data'] = await action(name, id, req.body);
66
+ }
67
+ next?.();
68
+ };
69
+ }
15
70
  export function createApp(db, options = {}) {
16
71
  // Create service
17
72
  const service = new Service(db);
@@ -23,69 +78,44 @@ export function createApp(db, options = {}) {
23
78
  ?.map((path) => (isAbsolute(path) ? path : join(process.cwd(), path)))
24
79
  .forEach((dir) => app.use(sirv(dir, { dev: !isProduction })));
25
80
  // CORS
26
- app.use(cors()).options('*', cors());
81
+ app
82
+ .use((req, res, next) => {
83
+ return cors({
84
+ allowedHeaders: req.headers['access-control-request-headers']
85
+ ?.split(',')
86
+ .map((h) => h.trim()),
87
+ })(req, res, next);
88
+ })
89
+ .options('*', cors());
27
90
  // Body parser
28
91
  app.use(json());
29
92
  app.get('/', (_req, res) => res.send(eta.render('index.html', { data: db.data })));
30
93
  app.get('/:name', (req, res, next) => {
31
94
  const { name = '' } = req.params;
32
- const query = Object.fromEntries(Object.entries(req.query)
33
- .map(([key, value]) => {
34
- if (['_start', '_end', '_limit', '_page', '_per_page'].includes(key) && typeof value === 'string') {
35
- return [key, parseInt(value)];
36
- }
37
- else {
38
- return [key, value];
39
- }
40
- })
41
- .filter(([_, value]) => !Number.isNaN(value)));
42
- res.locals['data'] = service.find(name, query);
43
- next();
95
+ const { where, sort, page, perPage, embed } = parseListParams(req);
96
+ res.locals['data'] = service.find(name, {
97
+ where,
98
+ sort,
99
+ page,
100
+ perPage,
101
+ embed,
102
+ });
103
+ next?.();
44
104
  });
45
105
  app.get('/:name/:id', (req, res, next) => {
46
106
  const { name = '', id = '' } = req.params;
47
107
  res.locals['data'] = service.findById(name, id, req.query);
48
- next();
49
- });
50
- app.post('/:name', async (req, res, next) => {
51
- const { name = '' } = req.params;
52
- if (isItem(req.body)) {
53
- res.locals['data'] = await service.create(name, req.body);
54
- }
55
- next();
56
- });
57
- app.put('/:name', async (req, res, next) => {
58
- const { name = '' } = req.params;
59
- if (isItem(req.body)) {
60
- res.locals['data'] = await service.update(name, req.body);
61
- }
62
- next();
63
- });
64
- app.put('/:name/:id', async (req, res, next) => {
65
- const { name = '', id = '' } = req.params;
66
- if (isItem(req.body)) {
67
- res.locals['data'] = await service.updateById(name, id, req.body);
68
- }
69
- next();
70
- });
71
- app.patch('/:name', async (req, res, next) => {
72
- const { name = '' } = req.params;
73
- if (isItem(req.body)) {
74
- res.locals['data'] = await service.patch(name, req.body);
75
- }
76
- next();
77
- });
78
- app.patch('/:name/:id', async (req, res, next) => {
79
- const { name = '', id = '' } = req.params;
80
- if (isItem(req.body)) {
81
- res.locals['data'] = await service.patchById(name, id, req.body);
82
- }
83
- next();
108
+ next?.();
84
109
  });
110
+ app.post('/:name', withBody(service.create.bind(service)));
111
+ app.put('/:name', withBody(service.update.bind(service)));
112
+ app.put('/:name/:id', withIdAndBody(service.updateById.bind(service)));
113
+ app.patch('/:name', withBody(service.patch.bind(service)));
114
+ app.patch('/:name/:id', withIdAndBody(service.patchById.bind(service)));
85
115
  app.delete('/:name/:id', async (req, res, next) => {
86
116
  const { name = '', id = '' } = req.params;
87
117
  res.locals['data'] = await service.destroyById(name, id, req.query['_dependent']);
88
- next();
118
+ next?.();
89
119
  });
90
120
  app.use('/:name', (req, res) => {
91
121
  const { data } = res.locals;