Skip to content

Commit f1c724d

Browse files
committed
feat: add middleware
1 parent 6aa56d9 commit f1c724d

File tree

6 files changed

+166
-12
lines changed

6 files changed

+166
-12
lines changed

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,68 @@ json-server -s ./static
202202
json-server -s ./static -s ./node_modules
203203
```
204204

205+
## Middleware
206+
207+
```sh
208+
json-server --middleware logger.mjs
209+
```
210+
211+
```js
212+
// logger.mjs
213+
import chalk from 'chalk';
214+
215+
export default (req, res, next) => {
216+
const currentDate = new Date().toISOString();
217+
console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`));
218+
219+
// Check if the request body is already parsed
220+
if (req.body && Object.keys(req.body).length > 0) {
221+
console.log(chalk.magenta('Body:'), req.body);
222+
} else {
223+
// Manually parse the request body if not already parsed
224+
let body = '';
225+
req.on('data', (chunk) => {
226+
body += chunk.toString();
227+
});
228+
req.on('end', () => {
229+
if (body) {
230+
try {
231+
const parsedBody = JSON.parse(body);
232+
console.log(chalk.magenta('Body:'), parsedBody);
233+
} catch (error) {
234+
console.log(chalk.red('Failed to parse body'), error);
235+
}
236+
}
237+
next();
238+
});
239+
return;
240+
}
241+
242+
next();
243+
};
244+
```
245+
246+
This will output:
247+
248+
```sh
249+
Index:
250+
http://localhost:3000/
251+
252+
Static files:
253+
Serving ./public directory if it exists
254+
255+
Endpoints:
256+
http://localhost:3000/posts
257+
http://localhost:3000/comments
258+
http://localhost:3000/profile
259+
260+
PATCH /posts/1 2025-01-03T08:25:13.138Z
261+
Body: { title: 'foo', body: 'bar', userId: 1 }
262+
POST /posts 2025-01-03T08:25:18.661Z
263+
Body: { title: 'foo', body: 'bar', userId: 1 }
264+
GET /posts 2025-01-03T08:25:20.159Z
265+
```
266+
205267
## Notable differences with v0.17
206268
207269
- `id` is always a string and will be generated for you if missing

middleware/logger.mjs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import chalk from 'chalk';
2+
3+
export default (req, res, next) => {
4+
const currentDate = new Date().toISOString();
5+
console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`));
6+
7+
// Check if the request body is already parsed
8+
if (req.body && Object.keys(req.body).length > 0) {
9+
console.log(chalk.magenta('Body:'), req.body);
10+
} else {
11+
// Manually parse the request body if not already parsed
12+
let body = '';
13+
req.on('data', (chunk) => {
14+
body += chunk.toString();
15+
});
16+
req.on('end', () => {
17+
if (body) {
18+
try {
19+
const parsedBody = JSON.parse(body);
20+
console.log(chalk.magenta('Body:'), parsedBody);
21+
} catch (error) {
22+
console.log(chalk.red('Failed to parse body'), error);
23+
}
24+
}
25+
next();
26+
});
27+
return;
28+
}
29+
30+
next();
31+
};

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"dev": "tsx watch src/bin.ts fixtures/db.json",
1919
"build": "rm -rf lib && tsc",
2020
"test": "node --import tsx/esm --test src/*.test.ts",
21+
"logger": "tsx watch src/bin.ts fixtures/db.json --middleware=middleware/logger.mjs",
2122
"lint": "eslint src",
2223
"prepare": "husky",
2324
"prepublishOnly": "npm run build"
@@ -60,4 +61,4 @@
6061
"sirv": "^2.0.4",
6162
"sort-on": "^6.1.0"
6263
}
63-
}
64+
}

src/app.ts

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const isProduction = process.env['NODE_ENV'] === 'production'
1616
export type AppOptions = {
1717
logger?: boolean
1818
static?: string[]
19+
middleware?: (req: unknown, res: unknown, next: unknown) => void
1920
}
2021

2122
const eta = new Eta({
@@ -36,6 +37,12 @@ export function createApp(db: Low<Data>, options: AppOptions = {}) {
3637
?.map((path) => (isAbsolute(path) ? path : join(process.cwd(), path)))
3738
.forEach((dir) => app.use(sirv(dir, { dev: !isProduction })))
3839

40+
// Use middleware if specified
41+
if (options.middleware) {
42+
console.log('app.ts: Using middleware', options.middleware)
43+
app.use(options.middleware)
44+
}
45+
3946
// CORS
4047
app
4148
.use((req, res, next) => {

src/bin.ts

+42-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
3-
import { extname } from 'node:path'
3+
import { extname, resolve } from 'node:path'
44
import { parseArgs } from 'node:util'
55

66
import chalk from 'chalk'
@@ -19,11 +19,12 @@ function help() {
1919
console.log(`Usage: json-server [options] <file>
2020
2121
Options:
22-
-p, --port <port> Port (default: 3000)
23-
-h, --host <host> Host (default: localhost)
24-
-s, --static <dir> Static files directory (multiple allowed)
25-
--help Show this message
26-
--version Show version number
22+
-p, --port <port> Port (default: 3000)
23+
-h, --host <host> Host (default: localhost)
24+
-s, --static <dir> Static files directory (multiple allowed)
25+
--middleware <file> Middleware file
26+
--help Show this message
27+
--version Show version number
2728
`)
2829
}
2930

@@ -33,6 +34,7 @@ function args(): {
3334
port: number
3435
host: string
3536
static: string[]
37+
middleware: string
3638
} {
3739
try {
3840
const { values, positionals } = parseArgs({
@@ -53,6 +55,10 @@ function args(): {
5355
multiple: true,
5456
default: [],
5557
},
58+
middleware: {
59+
type: 'string',
60+
default: '',
61+
},
5662
help: {
5763
type: 'boolean',
5864
},
@@ -97,9 +103,10 @@ function args(): {
97103
// App args and options
98104
return {
99105
file: positionals[0] ?? '',
100-
port: parseInt(values.port as string),
101-
host: values.host as string,
102-
static: values.static as string[],
106+
port: parseInt(values.port),
107+
host: values.host,
108+
static: values.static,
109+
middleware: values.middleware,
103110
}
104111
} catch (e) {
105112
if ((e as NodeJS.ErrnoException).code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
@@ -112,7 +119,19 @@ function args(): {
112119
}
113120
}
114121

115-
const { file, port, host, static: staticArr } = args()
122+
// Load middleware
123+
async function loadMiddleware(middlewarePath: string) {
124+
const resolvedPath = resolve(process.cwd(), middlewarePath)
125+
if (existsSync(resolvedPath)) {
126+
const middlewareModule = await import(resolvedPath)
127+
return middlewareModule.default || middlewareModule
128+
} else {
129+
console.error(`Middleware file not found: ${resolvedPath}`)
130+
process.exit(1)
131+
}
132+
}
133+
134+
const { file, port, host, static: staticArr, middleware } = args()
116135

117136
if (!existsSync(file)) {
118137
console.log(chalk.red(`File ${file} not found`))
@@ -139,8 +158,19 @@ const observer = new Observer(adapter)
139158
const db = new Low<Data>(observer, {})
140159
await db.read()
141160

161+
// Load middleware if specified
162+
let middlewareFunction
163+
if (middleware) {
164+
console.log(chalk.gray(`Loading middleware from ${middleware}`))
165+
middlewareFunction = await loadMiddleware(middleware)
166+
}
167+
142168
// Create app
143-
const app = createApp(db, { logger: false, static: staticArr })
169+
const app = createApp(db, {
170+
logger: false,
171+
static: staticArr,
172+
middleware: middlewareFunction,
173+
})
144174

145175
function logRoutes(data: Data) {
146176
console.log(chalk.bold('Endpoints:'))
@@ -157,6 +187,7 @@ function logRoutes(data: Data) {
157187
)
158188
.join('\n'),
159189
)
190+
console.log()
160191
}
161192

162193
const kaomojis = ['♡⸜(˶˃ ᵕ ˂˶)⸝♡', '♡( ◡‿◡ )', '( ˶ˆ ᗜ ˆ˵ )', '(˶ᵔ ᵕ ᵔ˶)']

test.http

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
###
2+
GET http://localhost:3000/posts
3+
4+
###
5+
POST http://localhost:3000/posts
6+
Content-Type: application/json
7+
8+
{
9+
"title": "foo",
10+
"body": "bar",
11+
"userId": 1
12+
}
13+
14+
###
15+
PATCH http://localhost:3000/posts/1
16+
Content-Type: application/json
17+
18+
{
19+
"title": "foo",
20+
"body": "bar",
21+
"userId": 1
22+
}

0 commit comments

Comments
 (0)