@kollors/deep-json-server 0.5.0 → 0.6.0
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/README.md +196 -109
- package/README.ru.md +196 -109
- package/index.js +7 -2
- package/package.json +1 -1
- package/src/cli.js +45 -44
- package/src/config.js +132 -35
- package/src/database.js +31 -2
- package/src/files.js +98 -8
- package/src/openapi/config.js +15 -1
- package/src/openapi/document.js +19 -33
- package/src/openapi/index.js +29 -18
- package/src/query/pagination.js +1 -7
- package/src/server.js +87 -77
- package/types/index.d.ts +11 -2
- package/types/src/config.d.ts +86 -0
- package/types/src/database.d.ts +5 -2
- package/types/src/files.d.ts +23 -4
- package/types/src/openapi/config.d.ts +1 -1
- package/types/src/openapi/document.d.ts +6 -7
- package/types/src/openapi/index.d.ts +9 -14
- package/types/src/query/pagination.d.ts +1 -6
- package/types/src/server.d.ts +11 -28
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[GitHub](https://github.com/kollors/deep-json-server) | [npm](https://www.npmjs.com/package/@kollors/deep-json-server)
|
|
6
6
|
|
|
7
|
-
A small
|
|
7
|
+
A small REST mock server with CRUD, pagination, deep filtering, recursive relationship embedding, binary files, and OpenAPI generation. Data can be stored in JSON files or memory, and relations are inferred from conventional keys such as `countryId`, `genreIds`, and `publisherIds`.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -14,6 +14,43 @@ Node.js 20 or newer is required.
|
|
|
14
14
|
npm install --save-dev @kollors/deep-json-server
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
Create the database file `mock/database.json` before startup:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"movies": [
|
|
24
|
+
{ "id": "1", "title": "Shadows of Ardenia" }
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Create the ESM module `server.config.js` next to `package.json`:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
export default {
|
|
33
|
+
database: {
|
|
34
|
+
path: 'mock/database.json',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Start the server:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx deep-json-server server.config.js
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The API is available at `http://127.0.0.1:4001` by default. For example, `GET http://127.0.0.1:4001/movies` returns this page:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"data": [{ "id": "1", "title": "Shadows of Ardenia" }],
|
|
50
|
+
"total": 1
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
17
54
|
## Configuration and startup
|
|
18
55
|
|
|
19
56
|
Create the ESM module `server.config.js`. The example below enables every feature:
|
|
@@ -35,6 +72,9 @@ export default {
|
|
|
35
72
|
},
|
|
36
73
|
server: {
|
|
37
74
|
host: '127.0.0.1',
|
|
75
|
+
logger: true,
|
|
76
|
+
maxFileSize: 100 * 1024 * 1024,
|
|
77
|
+
maxPageSize: 1000,
|
|
38
78
|
port: 4001,
|
|
39
79
|
},
|
|
40
80
|
};
|
|
@@ -42,25 +82,51 @@ export default {
|
|
|
42
82
|
|
|
43
83
|
Configuration keys:
|
|
44
84
|
|
|
45
|
-
| Key |
|
|
85
|
+
| Key | Condition | Purpose |
|
|
46
86
|
| --- | --- | --- |
|
|
47
|
-
| `database.path` |
|
|
48
|
-
| `database.
|
|
49
|
-
| `
|
|
50
|
-
| `files.
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
87
|
+
| `database.path` | Exactly one of `path` or `data` is required | Existing JSON database file |
|
|
88
|
+
| `database.data` | Exactly one of `path` or `data` is required | Database object stored in memory |
|
|
89
|
+
| `database.schema` | No | Path to JSON overrides or an object with request-validation and OpenAPI settings |
|
|
90
|
+
| `files.directory` | Together with `files.metadata` | Directory for binary contents on disk |
|
|
91
|
+
| `files.metadata` | Together with `files.directory` | JSON file containing file metadata on disk |
|
|
92
|
+
| `files.data` | Instead of the `directory` and `metadata` pair | In-memory files with `Uint8Array` contents |
|
|
93
|
+
| `openapi.path` | Required by the `--openapi` and `--openapi-only` CLI flags | Generated OpenAPI YAML file; the programmatic API can return a document without this path |
|
|
94
|
+
| `server.host` | No | Host used by the CLI, `server.openapi()`, and argument-less `server.fastify().listen()`; defaults to `127.0.0.1` |
|
|
95
|
+
| `server.logger` | No | Fastify logger settings; defaults to `true` |
|
|
96
|
+
| `server.maxFileSize` | No | Maximum uploaded-file size in bytes when file routes are enabled; defaults to 100 MiB |
|
|
97
|
+
| `server.maxPageSize` | No | Maximum allowed `_perPage` in the API and OpenAPI; defaults to `1000` |
|
|
98
|
+
| `server.port` | No | Port used by the CLI, `server.openapi()`, and argument-less `server.fastify().listen()`; defaults to `4001` |
|
|
99
|
+
|
|
100
|
+
`server.port` must be an integer from `0` to `65535`. The value `0` lets Fastify select an available port at runtime, but cannot be used to generate an OpenAPI server URL, which requires a port from `1` to `65535`. Both `server.maxFileSize` and `server.maxPageSize` must be positive integers.
|
|
54
101
|
|
|
55
102
|
All relative paths are resolved from the directory containing `server.config.js`, not from the current working directory. Unknown keys, empty paths and invalid value types are rejected before startup. The config is executable JavaScript, so it can read environment variables, import other modules and calculate values before exporting the object. A `.js` config with `export default` requires an ESM project (`"type": "module"`); in a CommonJS project, use the same contents in `server.config.mjs`.
|
|
56
103
|
|
|
57
|
-
|
|
104
|
+
The same config may keep everything in memory. `database.path` and `database.data` are mutually exclusive; `database.schema` accepts either a path or an object. Likewise, `files.data` cannot be combined with `files.directory` or `files.metadata`:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
export default {
|
|
108
|
+
database: {
|
|
109
|
+
data: { movies: [{ id: '1', title: 'Shadows of Ardenia' }] },
|
|
110
|
+
schema: { $info: { title: 'Movie API', version: '1.0.0' } },
|
|
111
|
+
},
|
|
112
|
+
files: {
|
|
113
|
+
data: [{ content: new Uint8Array([1, 2, 3]), id: 'file-1', mimeType: 'application/octet-stream', name: 'example.bin' }],
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
In-memory values are cloned during initialization. CRUD and file operations therefore do not mutate the exported config object, and their results disappear when the process exits.
|
|
119
|
+
|
|
120
|
+
Add the commands you need to `package.json`. Here, `mock:openapi:files` updates OpenAPI first and then keeps the server running with file routes:
|
|
58
121
|
|
|
59
122
|
```json
|
|
60
123
|
{
|
|
61
124
|
"scripts": {
|
|
62
|
-
"mock": "deep-json-server
|
|
63
|
-
"
|
|
125
|
+
"mock": "deep-json-server server.config.js",
|
|
126
|
+
"mock:files": "deep-json-server --files server.config.js",
|
|
127
|
+
"mock:openapi:files": "deep-json-server --files --openapi server.config.js",
|
|
128
|
+
"openapi": "deep-json-server --openapi-only server.config.js",
|
|
129
|
+
"openapi:files": "deep-json-server --files --openapi-only server.config.js"
|
|
64
130
|
}
|
|
65
131
|
}
|
|
66
132
|
```
|
|
@@ -71,24 +137,26 @@ CLI modes:
|
|
|
71
137
|
| --- | --- |
|
|
72
138
|
| `deep-json-server server.config.js` | Starts the CRUD server without file routes |
|
|
73
139
|
| `deep-json-server --files server.config.js` | Starts the CRUD server with file routes |
|
|
74
|
-
| `deep-json-server --openapi server.config.js` | Generates OpenAPI and
|
|
75
|
-
| `deep-json-server --
|
|
140
|
+
| `deep-json-server --openapi server.config.js` | Generates OpenAPI and starts the CRUD server |
|
|
141
|
+
| `deep-json-server --files --openapi server.config.js` | Generates OpenAPI with file routes and starts the server with them |
|
|
142
|
+
| `deep-json-server --openapi-only server.config.js` | Generates OpenAPI and exits |
|
|
143
|
+
| `deep-json-server --files --openapi-only server.config.js` | Generates OpenAPI with file routes and exits |
|
|
76
144
|
|
|
77
|
-
`--
|
|
145
|
+
`--files` is independent: without it, file routes are neither registered nor added to OpenAPI, even when the config contains a `files` section. The `--openapi` and `--openapi-only` flags are mutually exclusive. Run `deep-json-server --help` to print the CLI summary.
|
|
78
146
|
|
|
79
147
|
## Example database
|
|
80
148
|
|
|
81
|
-
|
|
149
|
+
Below is an example movie catalog with sample data. `Gangster` is linked to its parent genre, `Crime`.
|
|
82
150
|
|
|
83
151
|
```json
|
|
84
152
|
{
|
|
85
153
|
"countries": [
|
|
86
|
-
{ "id": "1", "isArchived": false, "name": "
|
|
87
|
-
{ "id": "2", "isArchived": false, "name": "
|
|
154
|
+
{ "id": "1", "isArchived": false, "name": "Ardenia" },
|
|
155
|
+
{ "id": "2", "isArchived": false, "name": "Veloria" }
|
|
88
156
|
],
|
|
89
157
|
"genres": [
|
|
90
158
|
{ "id": "1", "isArchived": false, "name": "Crime", "parentIds": [] },
|
|
91
|
-
{ "id": "2", "isArchived": false, "name": "Gangster
|
|
159
|
+
{ "id": "2", "isArchived": false, "name": "Gangster", "parentIds": ["1"] },
|
|
92
160
|
{ "id": "3", "isArchived": false, "name": "Drama", "parentIds": [] },
|
|
93
161
|
{ "id": "4", "isArchived": false, "name": "Comedy", "parentIds": [] }
|
|
94
162
|
],
|
|
@@ -98,39 +166,39 @@ This example is based on a movie catalog. `Gangster film` demonstrates a relatio
|
|
|
98
166
|
{ "genreIds": ["2", "3"], "id": "movie-1-actor-1", "userId": "1" },
|
|
99
167
|
{ "genreIds": ["3"], "id": "movie-1-actor-2", "userId": "2" }
|
|
100
168
|
],
|
|
101
|
-
"coverSrc": "https://
|
|
102
|
-
"description": "The
|
|
169
|
+
"coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
|
|
170
|
+
"description": "The heir to a port city uncovers a conspiracy between two rival families.",
|
|
103
171
|
"id": "1",
|
|
104
172
|
"isArchived": false,
|
|
105
173
|
"publisherIds": ["2"],
|
|
106
|
-
"title": "
|
|
174
|
+
"title": "Shadows of Ardenia"
|
|
107
175
|
},
|
|
108
176
|
{
|
|
109
177
|
"actors": [],
|
|
110
|
-
"coverSrc": "https://
|
|
111
|
-
"description": "
|
|
178
|
+
"coverSrc": "https://example.com/covers/northern-star.jpg",
|
|
179
|
+
"description": "A night manager at an old hotel is drawn into the search for a missing painting.",
|
|
112
180
|
"id": "2",
|
|
113
181
|
"isArchived": false,
|
|
114
182
|
"publisherIds": ["1"],
|
|
115
|
-
"title": "
|
|
183
|
+
"title": "Midnight at the Northern Star"
|
|
116
184
|
}
|
|
117
185
|
],
|
|
118
186
|
"publishers": [
|
|
119
|
-
{ "id": "1", "isArchived": false, "name": "
|
|
120
|
-
{ "id": "2", "isArchived": false, "name": "
|
|
187
|
+
{ "id": "1", "isArchived": false, "name": "Northlight Studio" },
|
|
188
|
+
{ "id": "2", "isArchived": false, "name": "Aurora Pictures" }
|
|
121
189
|
],
|
|
122
190
|
"users": [
|
|
123
191
|
{
|
|
124
|
-
"bornAt": "
|
|
192
|
+
"bornAt": "1988-03-14",
|
|
125
193
|
"countryId": "1",
|
|
126
|
-
"fullName": "
|
|
194
|
+
"fullName": "Mira Volkova",
|
|
127
195
|
"id": "1",
|
|
128
196
|
"isArchived": false
|
|
129
197
|
},
|
|
130
198
|
{
|
|
131
|
-
"bornAt": "
|
|
132
|
-
"countryId": "
|
|
133
|
-
"fullName": "
|
|
199
|
+
"bornAt": "1991-11-02",
|
|
200
|
+
"countryId": "2",
|
|
201
|
+
"fullName": "Leon Vetrov",
|
|
134
202
|
"id": "2",
|
|
135
203
|
"isArchived": false
|
|
136
204
|
}
|
|
@@ -149,14 +217,14 @@ PATCH /movies/:id
|
|
|
149
217
|
DELETE /movies/:id
|
|
150
218
|
```
|
|
151
219
|
|
|
152
|
-
`POST` generates a string ID. `PUT` completely replaces the selected record, while `PATCH` updates only supplied fields; both preserve the existing ID and its type. An `id` supplied in any request body cannot override the server-controlled ID. All write operations — `POST`, `PUT`, `PATCH` and `DELETE` — are serialized
|
|
220
|
+
`POST` generates a string ID. `PUT` completely replaces the selected record, while `PATCH` updates only supplied fields; both preserve the existing ID and its type. An `id` supplied in any request body cannot override the server-controlled ID. All write operations — `POST`, `PUT`, `PATCH` and `DELETE` — are serialized; disk storage persists them in JSON, while memory storage retains them until the process exits.
|
|
153
221
|
|
|
154
222
|
The database file must exist before startup. Resource names may contain Latin letters, numbers, `_` and `-`, and must start with a letter. Every resource is an array of JSON objects. Every record must have a non-empty string or finite numeric `id`; IDs must be unique within a resource when compared as strings, so `1` and `"1"` cannot coexist. The server rereads the file before every GET and write operation, so valid external edits become visible without a restart.
|
|
155
223
|
|
|
156
224
|
Successful writes return the created, replaced, updated or deleted record. Errors use an appropriate HTTP status and this JSON shape:
|
|
157
225
|
|
|
158
226
|
```json
|
|
159
|
-
{ "error": "
|
|
227
|
+
{ "error": "..." }
|
|
160
228
|
```
|
|
161
229
|
|
|
162
230
|
## Pagination and sorting
|
|
@@ -165,21 +233,18 @@ Successful writes return the created, replaced, updated or deleted record. Error
|
|
|
165
233
|
GET /movies?_page=1&_perPage=10&_sort=-id,title
|
|
166
234
|
```
|
|
167
235
|
|
|
168
|
-
A GET
|
|
236
|
+
A collection GET always returns the current page data and the total number of records after filtering. `_page` defaults to `1`, and `_perPage` defaults to `10`:
|
|
169
237
|
|
|
170
238
|
```json
|
|
171
239
|
{
|
|
172
240
|
"data": [],
|
|
173
|
-
"
|
|
174
|
-
"items": 0,
|
|
175
|
-
"last": 1,
|
|
176
|
-
"next": null,
|
|
177
|
-
"pages": 1,
|
|
178
|
-
"prev": null
|
|
241
|
+
"total": 0
|
|
179
242
|
}
|
|
180
243
|
```
|
|
181
244
|
|
|
182
|
-
|
|
245
|
+
`data` contains only the records on the requested page. `total` is the number of all records matching the filter before pagination is applied. When needed, a client can calculate the last page as `Math.max(1, Math.ceil(total / pageSize))`.
|
|
246
|
+
|
|
247
|
+
Both pagination parameters must be positive integers. `_perPage` cannot exceed `1000` by default; change the limit through `server.maxPageSize` in the config passed to either the CLI or `createServer()`. Invalid values return `400` instead of being silently corrected. A page beyond the last page returns an empty `data` array while preserving the actual `total` value.
|
|
183
248
|
|
|
184
249
|
`_sort` accepts comma-separated field paths. Rules are applied from left to right; prefix a field with `-` for descending order. Dot paths can address nested object fields, including fields added by `_embed`, for example `GET /users?_embed=country&_sort=country.name,-id`. Unknown or unsafe sort fields return `400`.
|
|
185
250
|
|
|
@@ -188,7 +253,7 @@ Both pagination parameters must be positive integers. `_perPage` cannot exceed `
|
|
|
188
253
|
Pass a JSON object through `_where`:
|
|
189
254
|
|
|
190
255
|
```http
|
|
191
|
-
GET /movies?_where={"title":{"contains":"
|
|
256
|
+
GET /movies?_where={"title":{"contains":"ardenia"}}
|
|
192
257
|
```
|
|
193
258
|
|
|
194
259
|
Nested objects and arrays can be filtered at any depth. Conditions in one object use `AND` by default:
|
|
@@ -196,7 +261,7 @@ Nested objects and arrays can be filtered at any depth. Conditions in one object
|
|
|
196
261
|
```json
|
|
197
262
|
{
|
|
198
263
|
"actors": { "some": { "userId": { "eq": "1" } } },
|
|
199
|
-
"title": { "contains": "
|
|
264
|
+
"title": { "contains": "ardenia" }
|
|
200
265
|
}
|
|
201
266
|
```
|
|
202
267
|
|
|
@@ -207,7 +272,7 @@ Use `and`, `or` and `not` for explicit logical groups:
|
|
|
207
272
|
"and": [
|
|
208
273
|
{
|
|
209
274
|
"or": [
|
|
210
|
-
{ "title": { "contains": "
|
|
275
|
+
{ "title": { "contains": "ardenia" } },
|
|
211
276
|
{ "actors": { "some": { "userId": { "eq": "2" } } } }
|
|
212
277
|
]
|
|
213
278
|
},
|
|
@@ -231,12 +296,14 @@ Field operators:
|
|
|
231
296
|
Simple query parameters are supported too:
|
|
232
297
|
|
|
233
298
|
```http
|
|
234
|
-
GET /movies?title:contains=
|
|
299
|
+
GET /movies?title:contains=ardenia
|
|
235
300
|
```
|
|
236
301
|
|
|
237
302
|
Simple filter values recognize JSON primitives: numbers, `true`, `false` and `null`. Values with leading zeroes, such as `001`, remain strings. Unknown operators, invalid logical conditions and filter paths that do not exist in a non-empty resource return `400`.
|
|
238
303
|
|
|
239
|
-
|
|
304
|
+
Multiple simple query filters are combined with `AND`. For an `in` filter, separate values with commas: `GET /movies?id:in=1,2`. On an array field, `in` means that at least one field element matches at least one supplied value. `every` returns `true` for an empty array, while `some` returns `false`.
|
|
305
|
+
|
|
306
|
+
If `_where` is present, it is the complete filter and other simple filter parameters are ignored. The examples show readable JSON; an HTTP client must URL-encode `_where` when constructing the URL manually, for example with `encodeURIComponent(JSON.stringify(where))`.
|
|
240
307
|
|
|
241
308
|
Filtering is performed after `_embed`. This means a filter can address fields added by an embedded relation when the same request includes that `_embed`; stored `...Id` and `...Ids` fields can always be filtered directly.
|
|
242
309
|
|
|
@@ -248,7 +315,7 @@ Use `_embed` to add related records to the response:
|
|
|
248
315
|
GET /movies/1?_embed=actors.user.country&_embed=actors.genres&_embed=publishers
|
|
249
316
|
```
|
|
250
317
|
|
|
251
|
-
The response contains actors, each actor's user and genres, the user's country, and publishers. Original ID fields remain in the response, and the database file is not modified.
|
|
318
|
+
The response contains actors, each actor's user and genres, the user's country, and publishers. Original ID fields remain in the response, and the database file is not modified. The server imposes no fixed depth limit, but every required level must be written explicitly in the finite `_embed` path:
|
|
252
319
|
|
|
253
320
|
```http
|
|
254
321
|
GET /movies/1?_embed=actors.user.country
|
|
@@ -285,35 +352,37 @@ Add `files.directory` and `files.metadata` to the server config, then pass `--fi
|
|
|
285
352
|
deep-json-server --files server.config.js
|
|
286
353
|
```
|
|
287
354
|
|
|
355
|
+
For temporary tests, use `files.data` instead. Each initial record contains `id`, `name`, `mimeType`, and binary `content` as a `Uint8Array`; `size` and `url` are derived automatically. Uploaded files then remain in memory until the process exits.
|
|
356
|
+
|
|
288
357
|
Upload one file as the request body. Both headers are required: `Content-Name` contains the relative logical name encoded with `encodeURIComponent`, and `Content-Type` contains the file MIME type:
|
|
289
358
|
|
|
290
359
|
```http
|
|
291
360
|
POST /_files
|
|
292
|
-
Content-Name: posters%
|
|
361
|
+
Content-Name: posters%2Fshadows-of-ardenia.jpg
|
|
293
362
|
Content-Type: image/jpeg
|
|
294
363
|
|
|
295
364
|
<binary body>
|
|
296
365
|
```
|
|
297
366
|
|
|
298
|
-
|
|
367
|
+
A successful upload returns status `201`, metadata, and a stable URL:
|
|
299
368
|
|
|
300
369
|
```json
|
|
301
370
|
{
|
|
302
371
|
"id": "generated-id",
|
|
303
372
|
"mimeType": "image/jpeg",
|
|
304
|
-
"name": "posters/
|
|
373
|
+
"name": "posters/shadows-of-ardenia.jpg",
|
|
305
374
|
"size": 182340,
|
|
306
375
|
"url": "/_files/generated-id"
|
|
307
376
|
}
|
|
308
377
|
```
|
|
309
378
|
|
|
310
|
-
|
|
379
|
+
`GET /_files/:id` returns the original bytes with status `200`. `DELETE /_files/:id` removes both the binary contents and metadata, and returns the deleted metadata with status `200`. The returned `url` is relative to the mock-server origin. The server creates the configured directories automatically, stores binary contents under generated IDs without relying on the original file name, and keeps the logical names and other metadata in `files.metadata`. The metadata file may be absent initially and is created on the first upload. Do not edit it while the server is running.
|
|
311
380
|
|
|
312
|
-
The upload is raw binary rather than `multipart/form-data`, so `XMLHttpRequest.upload.onprogress` can report progress while the browser sends a `File` directly with `xhr.send(file)`. The default maximum size is 100 MiB and can be changed through
|
|
381
|
+
The upload is raw binary rather than `multipart/form-data`, so `XMLHttpRequest.upload.onprogress` can report progress while the browser sends a `File` directly with `xhr.send(file)`. The default maximum size is 100 MiB and can be changed through `server.maxFileSize`. A missing or unsafe `Content-Name` returns `400`, an exceeded limit returns `413`, and a missing, malformed, or Fastify-unsupported `Content-Type` returns `400` or `415`, depending on which validation stage rejects it.
|
|
313
382
|
|
|
314
383
|
## Database schema and OpenAPI generation
|
|
315
384
|
|
|
316
|
-
The optional
|
|
385
|
+
The optional path or object in `database.schema` customizes inferred schemas. This is a Deep JSON Server configuration format, not a standard JSON Schema document: `$schema` is an object containing resource settings. For example, `mock/database-schema.json` may contain:
|
|
317
386
|
|
|
318
387
|
```json
|
|
319
388
|
{
|
|
@@ -349,12 +418,16 @@ Schema configuration:
|
|
|
349
418
|
| `$schema.<resource>.formats` | OpenAPI formats for inferred or explicit string fields, such as `date`, `date-time` or `uri` |
|
|
350
419
|
| `$schema.<resource>.properties` | Recursive OpenAPI-compatible field schemas merged with inference |
|
|
351
420
|
|
|
421
|
+
`formats` is shorthand for assigning `format` to an existing string field. `properties` can fully describe a field—including its `type`, `format`, constraints, and nested properties—or add a field that is absent from the data. If both mechanisms assign a format to the same field, the value from `formats` is applied last.
|
|
422
|
+
|
|
352
423
|
Set `openapi.path` in the server config, then generate an OpenAPI 3.0.3 file and exit:
|
|
353
424
|
|
|
354
425
|
```bash
|
|
355
|
-
deep-json-server --openapi
|
|
426
|
+
deep-json-server --openapi-only server.config.js
|
|
356
427
|
```
|
|
357
428
|
|
|
429
|
+
To include file routes in the document, configure the `files` section and add `--files`: `deep-json-server --files --openapi-only server.config.js`.
|
|
430
|
+
|
|
358
431
|
The generator infers resources and field types from all database records. Every inferred field is optional by default, while the top-level `id` is always required in response schemas and is omitted from create and update request schemas. Add other required fields to `required`. A nested required path marks that nested property as required; it does not automatically make every parent path required, so list the parent separately when necessary.
|
|
359
432
|
|
|
360
433
|
Different value types are inferred independently and combined through `oneOf`. Configuration is validated before generation: `$info`, resource and schema names, and the structure of `properties` are validated, while paths from `required` and `formats` must exist in the resulting schema.
|
|
@@ -377,7 +450,7 @@ Use `properties` to describe fields that cannot be inferred, particularly for an
|
|
|
377
450
|
|
|
378
451
|
An empty resource still receives a required string `id` property because IDs created by the server are strings. Generation stops with an actionable error when resources produce duplicate schema names or operation IDs; use an explicit `name` to resolve schema-name collisions. The output directory is created automatically, and the configured YAML file is replaced on every generation.
|
|
379
452
|
|
|
380
|
-
`$info` becomes the OpenAPI `info` object, while resource settings live under `$schema`.
|
|
453
|
+
`$info` becomes the OpenAPI `info` object, while resource settings live under `$schema`. In the CLI, the OpenAPI `servers` entry uses `server.host` and `server.port`, then the `HOST` and `PORT` environment-variable fallbacks, and finally `http://127.0.0.1:4001`. A direct `createServer()` call does not read those environment variables automatically: `server.openapi()` uses the config values or the same default URL.
|
|
381
454
|
|
|
382
455
|
Use `name` when a resource needs an explicit schema name instead of the automatically singularized name:
|
|
383
456
|
|
|
@@ -391,73 +464,87 @@ Use `name` when a resource needs an explicit schema name instead of the automati
|
|
|
391
464
|
}
|
|
392
465
|
```
|
|
393
466
|
|
|
394
|
-
The generated document describes CRUD endpoints, pagination, sorting, deep filters, `_embed`, and both direct and reverse response relations inferred from `...Id` and `...Ids` fields. When `--files` is present, it also describes raw binary upload, download and deletion endpoints. A numeric database ID is described as `integer | string`, because a later `POST` creates a string ID in the same resource. The document can be used as input for tools such as RTK Query OpenAPI Codegen. OpenAPI is generated only
|
|
467
|
+
The generated document describes CRUD endpoints, pagination, sorting, deep filters, `_embed`, and both direct and reverse response relations inferred from `...Id` and `...Ids` fields. When `--files` is present, it also describes raw binary upload, download and deletion endpoints. A numeric database ID is described as `integer | string`, because a later `POST` creates a string ID in the same resource. The document can be used as input for tools such as RTK Query OpenAPI Codegen. OpenAPI is generated only with `--openapi` or `--openapi-only`; normal server startup does not rewrite the file.
|
|
395
468
|
|
|
396
469
|
During normal startup, request bodies are validated against the same inferred and configured schemas. `POST` and `PUT` enforce configured required fields; `PATCH` validates only fields that are actually supplied. `formats` and `properties` apply to all three methods. Unlisted additional object fields remain allowed. Invalid bodies return `400`.
|
|
397
470
|
|
|
398
|
-
## Scope and security
|
|
399
|
-
|
|
400
|
-
Deep JSON Server is intended for local development and automated tests. It has no authentication or authorization, allows CORS from every origin, persists accepted writes directly to the configured files and does not enforce referential integrity. Keep the default loopback host unless the surrounding environment provides its own access controls; do not expose the server or file routes to an untrusted network.
|
|
401
|
-
|
|
402
471
|
## Programmatic API
|
|
403
472
|
|
|
404
473
|
```js
|
|
405
|
-
import {
|
|
406
|
-
|
|
407
|
-
// Create an instance without opening a port, for example for tests.
|
|
408
|
-
const server = await createServer({
|
|
409
|
-
databasePath: 'mock/database.json',
|
|
410
|
-
filesDirectoryPath: 'mock/files',
|
|
411
|
-
filesMetadataPath: 'mock/files/_database.json',
|
|
412
|
-
logger: false,
|
|
413
|
-
maxFileSize: 100 * 1024 * 1024,
|
|
414
|
-
maxPageSize: 1000,
|
|
415
|
-
schemaPath: 'mock/database-schema.json',
|
|
416
|
-
});
|
|
474
|
+
import { createServer } from '@kollors/deep-json-server';
|
|
417
475
|
|
|
418
|
-
const
|
|
476
|
+
const config = {
|
|
477
|
+
database: {
|
|
478
|
+
path: 'mock/database.json',
|
|
479
|
+
schema: 'mock/database-schema.json',
|
|
480
|
+
},
|
|
481
|
+
files: {
|
|
482
|
+
directory: 'mock/files',
|
|
483
|
+
metadata: 'mock/files/_database.json',
|
|
484
|
+
},
|
|
485
|
+
openapi: {
|
|
486
|
+
path: 'mock/openapi-schema.yaml',
|
|
487
|
+
},
|
|
488
|
+
server: {
|
|
489
|
+
host: '127.0.0.1',
|
|
490
|
+
logger: false,
|
|
491
|
+
maxFileSize: 100 * 1024 * 1024,
|
|
492
|
+
maxPageSize: 1000,
|
|
493
|
+
port: 4001,
|
|
494
|
+
},
|
|
495
|
+
};
|
|
419
496
|
|
|
420
|
-
|
|
497
|
+
// Make a request without opening a network port—useful in automated tests.
|
|
498
|
+
const server = await createServer(config);
|
|
499
|
+
const fastify = server.fastify();
|
|
500
|
+
const response = await fastify.inject({ method: 'GET', url: '/movies' });
|
|
421
501
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
port: 4001,
|
|
427
|
-
schemaPath: 'mock/database-schema.json',
|
|
428
|
-
});
|
|
502
|
+
console.log(response.json());
|
|
503
|
+
|
|
504
|
+
// Return the document and write it to config.openapi.path.
|
|
505
|
+
const document = await server.openapi();
|
|
429
506
|
|
|
430
|
-
await
|
|
507
|
+
await fastify.close();
|
|
431
508
|
|
|
432
|
-
//
|
|
433
|
-
await
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
509
|
+
// Start a network server. With no arguments, listen uses server.host and server.port.
|
|
510
|
+
const runningServer = await createServer(config);
|
|
511
|
+
const runningFastify = runningServer.fastify();
|
|
512
|
+
|
|
513
|
+
await runningFastify.listen();
|
|
514
|
+
|
|
515
|
+
// Later, during application shutdown:
|
|
516
|
+
await runningFastify.close();
|
|
517
|
+
|
|
518
|
+
// Keep the database, schema, and files entirely in memory.
|
|
519
|
+
const memoryServer = await createServer({
|
|
520
|
+
database: {
|
|
521
|
+
data: { movies: [{ id: '1', title: 'Shadows of Ardenia' }] },
|
|
522
|
+
schema: { $info: { title: 'Movie API', version: '1.0.0' } },
|
|
523
|
+
},
|
|
524
|
+
files: {
|
|
525
|
+
data: [{ content: new Uint8Array([1, 2, 3]), id: 'file-1', mimeType: 'application/octet-stream', name: 'example.bin' }],
|
|
526
|
+
},
|
|
440
527
|
});
|
|
441
528
|
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
);
|
|
529
|
+
const memoryFastify = memoryServer.fastify();
|
|
530
|
+
const memoryResponse = await memoryFastify.inject({ method: 'GET', url: '/movies/1' });
|
|
531
|
+
|
|
532
|
+
console.log(memoryResponse.json());
|
|
533
|
+
|
|
534
|
+
await memoryFastify.close();
|
|
448
535
|
```
|
|
449
536
|
|
|
450
|
-
|
|
537
|
+
`createServer()` accepts exactly the same config shape as `server.config.js`. It loads and clones the configured sources, then returns a facade with two operations:
|
|
451
538
|
|
|
452
|
-
|
|
|
539
|
+
| Member | Meaning |
|
|
453
540
|
| --- | --- | --- |
|
|
454
|
-
| `
|
|
455
|
-
| `
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
541
|
+
| `server.fastify()` | Lazily creates and caches the real Fastify instance; every native method remains available, and argument-less `listen()` uses `server.host` and `server.port` |
|
|
542
|
+
| `server.openapi()` | Returns an OpenAPI document and also writes it when `openapi.path` is configured |
|
|
543
|
+
|
|
544
|
+
File routes are enabled programmatically when a `files` section is present. The second argument has the shape `{ files?: boolean }`: pass `{ files: false }` to keep a configured store disabled, or `{ files: true }` to require a `files` section and enable the routes. `server.openapi()` uses the same feature state as `server.fastify()`.
|
|
545
|
+
|
|
546
|
+
An argument-less `server.fastify().listen()` uses `server.host` and `server.port`, falling back to `127.0.0.1:4001`. Explicit `listen(options)` values take precedence. Relative paths passed directly to `createServer()` resolve from the current working directory; paths loaded from `server.config.js` resolve from the config directory. The package includes generated TypeScript declarations for the facade and every config variant.
|
|
547
|
+
|
|
548
|
+
## Scope and security
|
|
549
|
+
|
|
550
|
+
Deep JSON Server is intended for local development and automated tests. It has no authentication or authorization, allows CORS from every origin, persists accepted writes when disk storage is configured and does not enforce referential integrity. Keep the default loopback host unless the surrounding environment provides its own access controls; do not expose the server or file routes to an untrusted network.
|