json-server 1.0.0-alpha.10 → 1.0.0-alpha.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/README.md +10 -3
- package/lib/bin.js +45 -15
- package/lib/observer.d.ts +1 -1
- package/lib/observer.js +1 -1
- package/lib/service.js +20 -1
- package/lib/service.test.js +11 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
10
|
```shell
|
|
11
|
-
npm install json-server
|
|
11
|
+
npm install json-server
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
@@ -40,7 +40,7 @@ $ json-server db.json
|
|
|
40
40
|
Get a REST API
|
|
41
41
|
|
|
42
42
|
```shell
|
|
43
|
-
$ curl
|
|
43
|
+
$ curl http://localhost:3000/posts/1
|
|
44
44
|
{
|
|
45
45
|
"id": "1",
|
|
46
46
|
"title": "a title"
|
|
@@ -49,15 +49,20 @@ $ curl -H "Accept: application/json" -X GET http://localhost:3000/posts/1
|
|
|
49
49
|
|
|
50
50
|
Run `json-server --help` for a list of options
|
|
51
51
|
|
|
52
|
+
## Sponsors ✨
|
|
53
|
+
|
|
52
54
|
| Sponsors |
|
|
53
55
|
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
|
54
56
|
| <a href="https://mockend.com/" target="_blank"><img src="https://jsonplaceholder.typicode.com/mockend.svg" height="70px"></a> |
|
|
55
57
|
| <a href="https://www.storyblok.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/c6b10674-4ada-4616-91b8-59d30046b45a" height="40px"></a> |
|
|
58
|
+
| <a href="https://betterstack.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/44679f8f-9671-470d-b77e-26d90b90cbdc" height="40px"></a> |
|
|
56
59
|
|
|
57
60
|
[Become a sponsor and have your company logo here](https://github.com/users/typicode/sponsorship)
|
|
58
61
|
|
|
59
62
|
## Routes
|
|
60
63
|
|
|
64
|
+
Based on the example `db.json`, you'll get the following routes:
|
|
65
|
+
|
|
61
66
|
```
|
|
62
67
|
GET /posts
|
|
63
68
|
GET /posts/:id
|
|
@@ -65,6 +70,8 @@ POST /posts
|
|
|
65
70
|
PUT /posts/:id
|
|
66
71
|
PATCH /posts/:id
|
|
67
72
|
DELETE /posts/:id
|
|
73
|
+
|
|
74
|
+
# Same for comments
|
|
68
75
|
```
|
|
69
76
|
|
|
70
77
|
```
|
|
@@ -124,7 +131,7 @@ GET /posts?_sort=id,-views
|
|
|
124
131
|
```
|
|
125
132
|
GET /posts?author.name=foo
|
|
126
133
|
GET /posts?author.email=foo
|
|
127
|
-
GET /posts?
|
|
134
|
+
GET /posts?tags[0]=foo
|
|
128
135
|
```
|
|
129
136
|
|
|
130
137
|
### Embed
|
package/lib/bin.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { extname, join } from 'node:path';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
|
+
import chalk from 'chalk';
|
|
5
6
|
import { watch } from 'chokidar';
|
|
6
7
|
import JSON5 from 'json5';
|
|
7
8
|
import { Low } from 'lowdb';
|
|
@@ -54,7 +55,7 @@ const file = positionals[0] ?? '';
|
|
|
54
55
|
const port = parseInt(values.port ?? process.env['PORT'] ?? '3000');
|
|
55
56
|
const host = values.host ?? process.env['HOST'] ?? 'localhost';
|
|
56
57
|
if (!existsSync(file)) {
|
|
57
|
-
console.log(`File ${file} not found`);
|
|
58
|
+
console.log(chalk.red(`File ${file} not found`));
|
|
58
59
|
process.exit(1);
|
|
59
60
|
}
|
|
60
61
|
// Set up database
|
|
@@ -73,37 +74,66 @@ const db = new Low(observer, {});
|
|
|
73
74
|
await db.read();
|
|
74
75
|
// Create app
|
|
75
76
|
const app = createApp(db, { logger: false, static: values.static });
|
|
76
|
-
function
|
|
77
|
-
|
|
77
|
+
function logRoutes(data) {
|
|
78
|
+
console.log([
|
|
79
|
+
chalk.bold('Endpoints:'),
|
|
80
|
+
...Object.keys(data).map((key) => `${chalk.gray(`http://${host}:${port}/`)}${chalk.blue(key)}`),
|
|
81
|
+
].join('\n'));
|
|
78
82
|
}
|
|
83
|
+
const kaomojis = ['♡⸜(˶˃ ᵕ ˂˶)⸝♡', '♡( ◡‿◡ )', '( ˶ˆ ᗜ ˆ˵ )', '(˶ᵔ ᵕ ᵔ˶)'];
|
|
84
|
+
function randomItem(items) {
|
|
85
|
+
const index = Math.floor(Math.random() * items.length);
|
|
86
|
+
return items.at(index) ?? '';
|
|
87
|
+
}
|
|
88
|
+
app.listen(port, () => {
|
|
89
|
+
console.log([
|
|
90
|
+
chalk.bold(`JSON Server started on PORT :${port}`),
|
|
91
|
+
chalk.gray('Press CTRL-C to stop'),
|
|
92
|
+
chalk.gray(`Watching ${file}...`),
|
|
93
|
+
'',
|
|
94
|
+
chalk.magenta(randomItem(kaomojis)),
|
|
95
|
+
'',
|
|
96
|
+
chalk.bold('Index:'),
|
|
97
|
+
chalk.gray(`http://localhost:${port}/`),
|
|
98
|
+
'',
|
|
99
|
+
chalk.bold('Static files:'),
|
|
100
|
+
chalk.gray('Serving ./public directory if it exists'),
|
|
101
|
+
'',
|
|
102
|
+
].join('\n'));
|
|
103
|
+
logRoutes(db.data);
|
|
104
|
+
});
|
|
79
105
|
// Watch file for changes
|
|
80
106
|
if (process.env['NODE_ENV'] !== 'production') {
|
|
81
107
|
let writing = false; // true if the file is being written to by the app
|
|
108
|
+
let prevEndpoints = '';
|
|
82
109
|
observer.onWriteStart = () => {
|
|
83
110
|
writing = true;
|
|
84
111
|
};
|
|
85
112
|
observer.onWriteEnd = () => {
|
|
86
113
|
writing = false;
|
|
87
114
|
};
|
|
88
|
-
observer.onReadStart = () =>
|
|
89
|
-
|
|
115
|
+
observer.onReadStart = () => {
|
|
116
|
+
prevEndpoints = JSON.stringify(Object.keys(db.data).sort());
|
|
117
|
+
};
|
|
118
|
+
observer.onReadEnd = (data) => {
|
|
119
|
+
if (data === null) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const nextEndpoints = JSON.stringify(Object.keys(data).sort());
|
|
123
|
+
if (prevEndpoints !== nextEndpoints) {
|
|
124
|
+
console.log();
|
|
125
|
+
logRoutes(data);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
90
128
|
watch(file).on('change', () => {
|
|
91
129
|
// Do no reload if the file is being written to by the app
|
|
92
130
|
if (!writing) {
|
|
93
|
-
db.read()
|
|
94
|
-
.then(() => routes(db))
|
|
95
|
-
.catch((e) => {
|
|
131
|
+
db.read().catch((e) => {
|
|
96
132
|
if (e instanceof SyntaxError) {
|
|
97
|
-
return console.log(e.message);
|
|
133
|
+
return console.log(chalk.red(['', `Error parsing ${file}`, e.message].join('\n')));
|
|
98
134
|
}
|
|
99
135
|
console.log(e);
|
|
100
136
|
});
|
|
101
137
|
}
|
|
102
138
|
});
|
|
103
139
|
}
|
|
104
|
-
app.listen(port, () => {
|
|
105
|
-
console.log(`Started on :${port}`);
|
|
106
|
-
console.log(`http://localhost:${port}/`);
|
|
107
|
-
console.log(routes(db).join('\n'));
|
|
108
|
-
console.log(`Watching ${file}...`);
|
|
109
|
-
});
|
package/lib/observer.d.ts
CHANGED
package/lib/observer.js
CHANGED
package/lib/service.js
CHANGED
|
@@ -72,9 +72,28 @@ function deleteDependents(db, name, dependents) {
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
function randomId() {
|
|
76
|
+
return randomBytes(2).toString('hex');
|
|
77
|
+
}
|
|
78
|
+
function ensureItemsHaveIds(items) {
|
|
79
|
+
return items.map((item) => {
|
|
80
|
+
if (item['id'] === undefined) {
|
|
81
|
+
return { ...item, id: randomId() };
|
|
82
|
+
}
|
|
83
|
+
return item;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Ensure all items have an id
|
|
87
|
+
function ensureAllItemsHaveIds(data) {
|
|
88
|
+
return Object.entries(data).reduce((acc, [key, value]) => ({
|
|
89
|
+
...acc,
|
|
90
|
+
[key]: Array.isArray(value) ? ensureItemsHaveIds(value) : value,
|
|
91
|
+
}), {});
|
|
92
|
+
}
|
|
75
93
|
export class Service {
|
|
76
94
|
#db;
|
|
77
95
|
constructor(db) {
|
|
96
|
+
db.data = ensureAllItemsHaveIds(db.data);
|
|
78
97
|
this.#db = db;
|
|
79
98
|
}
|
|
80
99
|
#get(name) {
|
|
@@ -226,7 +245,7 @@ export class Service {
|
|
|
226
245
|
const items = this.#get(name);
|
|
227
246
|
if (items === undefined || !Array.isArray(items))
|
|
228
247
|
return;
|
|
229
|
-
const item = { id:
|
|
248
|
+
const item = { id: randomId(), ...data };
|
|
230
249
|
items.push(item);
|
|
231
250
|
await this.#db.write();
|
|
232
251
|
return item;
|
package/lib/service.test.js
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import { Low, Memory } from 'lowdb';
|
|
4
4
|
import { Service } from './service.js';
|
|
5
|
-
const defaultData = { posts: [] };
|
|
5
|
+
const defaultData = { posts: [], comments: [], object: {} };
|
|
6
6
|
const adapter = new Memory();
|
|
7
7
|
const db = new Low(adapter, defaultData);
|
|
8
8
|
const service = new Service(db);
|
|
@@ -44,6 +44,16 @@ function reset() {
|
|
|
44
44
|
object: obj,
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
+
await test('constructor', () => {
|
|
48
|
+
const defaultData = { posts: [{ id: '1' }, {}], object: {} };
|
|
49
|
+
const db = new Low(adapter, defaultData);
|
|
50
|
+
new Service(db);
|
|
51
|
+
if (Array.isArray(db.data['posts'])) {
|
|
52
|
+
const id = db.data['posts']?.at(1)?.['id'];
|
|
53
|
+
assert.ok(id instanceof String, 'id should be a string');
|
|
54
|
+
assert.ok(id.length > 0, 'id should not be empty');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
47
57
|
await test('findById', () => {
|
|
48
58
|
reset();
|
|
49
59
|
if (!Array.isArray(db.data?.[POSTS]))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-server",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.12",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "lib",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@tinyhttp/app": "^2.2.1",
|
|
41
41
|
"@tinyhttp/cors": "^2.0.0",
|
|
42
|
+
"chalk": "^5.3.0",
|
|
42
43
|
"chokidar": "^3.5.3",
|
|
43
44
|
"dot-prop": "^8.0.2",
|
|
44
45
|
"eta": "^3.2.0",
|