From f1c724dba619abaae0f91a859c99f0648d11ef7d Mon Sep 17 00:00:00 2001 From: "remi.kristelijn" Date: Fri, 3 Jan 2025 09:28:21 +0100 Subject: [PATCH 1/7] feat: add middleware --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++ middleware/logger.mjs | 31 ++++++++++++++++++++++ package.json | 3 ++- src/app.ts | 7 +++++ src/bin.ts | 53 ++++++++++++++++++++++++++++-------- test.http | 22 +++++++++++++++ 6 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 middleware/logger.mjs create mode 100644 test.http diff --git a/README.md b/README.md index 2d894e32b..83a4a3027 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,68 @@ json-server -s ./static json-server -s ./static -s ./node_modules ``` +## Middleware + +```sh +json-server --middleware logger.mjs +``` + +```js +// logger.mjs +import chalk from 'chalk'; + +export default (req, res, next) => { + const currentDate = new Date().toISOString(); + console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`)); + + // Check if the request body is already parsed + if (req.body && Object.keys(req.body).length > 0) { + console.log(chalk.magenta('Body:'), req.body); + } else { + // Manually parse the request body if not already parsed + let body = ''; + req.on('data', (chunk) => { + body += chunk.toString(); + }); + req.on('end', () => { + if (body) { + try { + const parsedBody = JSON.parse(body); + console.log(chalk.magenta('Body:'), parsedBody); + } catch (error) { + console.log(chalk.red('Failed to parse body'), error); + } + } + next(); + }); + return; + } + + next(); +}; +``` + +This will output: + +```sh +Index: +http://localhost:3000/ + +Static files: +Serving ./public directory if it exists + +Endpoints: +http://localhost:3000/posts +http://localhost:3000/comments +http://localhost:3000/profile + +PATCH /posts/1 2025-01-03T08:25:13.138Z +Body: { title: 'foo', body: 'bar', userId: 1 } +POST /posts 2025-01-03T08:25:18.661Z +Body: { title: 'foo', body: 'bar', userId: 1 } +GET /posts 2025-01-03T08:25:20.159Z +``` + ## Notable differences with v0.17 - `id` is always a string and will be generated for you if missing diff --git a/middleware/logger.mjs b/middleware/logger.mjs new file mode 100644 index 000000000..4fcfd8a6d --- /dev/null +++ b/middleware/logger.mjs @@ -0,0 +1,31 @@ +import chalk from 'chalk'; + +export default (req, res, next) => { + const currentDate = new Date().toISOString(); + console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`)); + + // Check if the request body is already parsed + if (req.body && Object.keys(req.body).length > 0) { + console.log(chalk.magenta('Body:'), req.body); + } else { + // Manually parse the request body if not already parsed + let body = ''; + req.on('data', (chunk) => { + body += chunk.toString(); + }); + req.on('end', () => { + if (body) { + try { + const parsedBody = JSON.parse(body); + console.log(chalk.magenta('Body:'), parsedBody); + } catch (error) { + console.log(chalk.red('Failed to parse body'), error); + } + } + next(); + }); + return; + } + + next(); +}; \ No newline at end of file diff --git a/package.json b/package.json index a0cfb2993..c325f3a4d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dev": "tsx watch src/bin.ts fixtures/db.json", "build": "rm -rf lib && tsc", "test": "node --import tsx/esm --test src/*.test.ts", + "logger": "tsx watch src/bin.ts fixtures/db.json --middleware=middleware/logger.mjs", "lint": "eslint src", "prepare": "husky", "prepublishOnly": "npm run build" @@ -60,4 +61,4 @@ "sirv": "^2.0.4", "sort-on": "^6.1.0" } -} +} \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index b8c5e79e7..55717da9e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,6 +16,7 @@ const isProduction = process.env['NODE_ENV'] === 'production' export type AppOptions = { logger?: boolean static?: string[] + middleware?: (req: unknown, res: unknown, next: unknown) => void } const eta = new Eta({ @@ -36,6 +37,12 @@ export function createApp(db: Low, options: AppOptions = {}) { ?.map((path) => (isAbsolute(path) ? path : join(process.cwd(), path))) .forEach((dir) => app.use(sirv(dir, { dev: !isProduction }))) + // Use middleware if specified + if (options.middleware) { + console.log('app.ts: Using middleware', options.middleware) + app.use(options.middleware) + } + // CORS app .use((req, res, next) => { diff --git a/src/bin.ts b/src/bin.ts index 4633e5e43..d128b73ae 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node import { existsSync, readFileSync, writeFileSync } from 'node:fs' -import { extname } from 'node:path' +import { extname, resolve } from 'node:path' import { parseArgs } from 'node:util' import chalk from 'chalk' @@ -19,11 +19,12 @@ function help() { console.log(`Usage: json-server [options] Options: - -p, --port Port (default: 3000) - -h, --host Host (default: localhost) - -s, --static Static files directory (multiple allowed) - --help Show this message - --version Show version number + -p, --port Port (default: 3000) + -h, --host Host (default: localhost) + -s, --static Static files directory (multiple allowed) + --middleware Middleware file + --help Show this message + --version Show version number `) } @@ -33,6 +34,7 @@ function args(): { port: number host: string static: string[] + middleware: string } { try { const { values, positionals } = parseArgs({ @@ -53,6 +55,10 @@ function args(): { multiple: true, default: [], }, + middleware: { + type: 'string', + default: '', + }, help: { type: 'boolean', }, @@ -97,9 +103,10 @@ function args(): { // App args and options return { file: positionals[0] ?? '', - port: parseInt(values.port as string), - host: values.host as string, - static: values.static as string[], + port: parseInt(values.port), + host: values.host, + static: values.static, + middleware: values.middleware, } } catch (e) { if ((e as NodeJS.ErrnoException).code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') { @@ -112,7 +119,19 @@ function args(): { } } -const { file, port, host, static: staticArr } = args() +// Load middleware +async function loadMiddleware(middlewarePath: string) { + const resolvedPath = resolve(process.cwd(), middlewarePath) + if (existsSync(resolvedPath)) { + const middlewareModule = await import(resolvedPath) + return middlewareModule.default || middlewareModule + } else { + console.error(`Middleware file not found: ${resolvedPath}`) + process.exit(1) + } +} + +const { file, port, host, static: staticArr, middleware } = args() if (!existsSync(file)) { console.log(chalk.red(`File ${file} not found`)) @@ -139,8 +158,19 @@ const observer = new Observer(adapter) const db = new Low(observer, {}) await db.read() +// Load middleware if specified +let middlewareFunction +if (middleware) { + console.log(chalk.gray(`Loading middleware from ${middleware}`)) + middlewareFunction = await loadMiddleware(middleware) +} + // Create app -const app = createApp(db, { logger: false, static: staticArr }) +const app = createApp(db, { + logger: false, + static: staticArr, + middleware: middlewareFunction, +}) function logRoutes(data: Data) { console.log(chalk.bold('Endpoints:')) @@ -157,6 +187,7 @@ function logRoutes(data: Data) { ) .join('\n'), ) + console.log() } const kaomojis = ['♡⸜(˶˃ ᵕ ˂˶)⸝♡', '♡( ◡‿◡ )', '( ˶ˆ ᗜ ˆ˵ )', '(˶ᵔ ᵕ ᵔ˶)'] diff --git a/test.http b/test.http new file mode 100644 index 000000000..78fed90f9 --- /dev/null +++ b/test.http @@ -0,0 +1,22 @@ +### +GET http://localhost:3000/posts + +### +POST http://localhost:3000/posts +Content-Type: application/json + +{ + "title": "foo", + "body": "bar", + "userId": 1 +} + +### +PATCH http://localhost:3000/posts/1 +Content-Type: application/json + +{ + "title": "foo", + "body": "bar", + "userId": 1 +} \ No newline at end of file From a44eb49f549026e09881122d0b6eeeebe4362f0b Mon Sep 17 00:00:00 2001 From: "remi.kristelijn" Date: Fri, 3 Jan 2025 09:40:11 +0100 Subject: [PATCH 2/7] refactor: cleanup code --- src/app.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 55717da9e..5eaaa4d6e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -39,7 +39,6 @@ export function createApp(db: Low, options: AppOptions = {}) { // Use middleware if specified if (options.middleware) { - console.log('app.ts: Using middleware', options.middleware) app.use(options.middleware) } From d32f76d4c04d9aac6eb8e65c6e77248f86fda3bb Mon Sep 17 00:00:00 2001 From: "remi.kristelijn" Date: Fri, 3 Jan 2025 09:41:42 +0100 Subject: [PATCH 3/7] docs: tune docs --- README.md | 2 +- middleware/logger.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 83a4a3027..d77c6408d 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ json-server --middleware logger.mjs // logger.mjs import chalk from 'chalk'; -export default (req, res, next) => { +export default (req, _res, next) => { const currentDate = new Date().toISOString(); console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`)); diff --git a/middleware/logger.mjs b/middleware/logger.mjs index 4fcfd8a6d..a4a7f2210 100644 --- a/middleware/logger.mjs +++ b/middleware/logger.mjs @@ -1,6 +1,6 @@ import chalk from 'chalk'; -export default (req, res, next) => { +export default (req, _res, next) => { const currentDate = new Date().toISOString(); console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`)); From 595465ecec3b8b0fb60924d807669aaca7f86ee6 Mon Sep 17 00:00:00 2001 From: Sequencer <45060278+sequencerr@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:51:51 +0400 Subject: [PATCH 4/7] Upd clean feature patch changes --- README.md | 63 +++++++------------------------------------ middleware/logger.mjs | 31 --------------------- package.json | 3 +-- src/app.ts | 6 ++--- src/bin.ts | 43 ++++++++++++++--------------- test.http | 22 --------------- 6 files changed, 32 insertions(+), 136 deletions(-) delete mode 100644 middleware/logger.mjs delete mode 100644 test.http diff --git a/README.md b/README.md index d77c6408d..906a63b4c 100644 --- a/README.md +++ b/README.md @@ -204,64 +204,19 @@ json-server -s ./static -s ./node_modules ## Middleware -```sh -json-server --middleware logger.mjs -``` +You can add your middlewares from the CLI using `--middleware` option: ```js -// logger.mjs -import chalk from 'chalk'; - -export default (req, _res, next) => { - const currentDate = new Date().toISOString(); - console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`)); - - // Check if the request body is already parsed - if (req.body && Object.keys(req.body).length > 0) { - console.log(chalk.magenta('Body:'), req.body); - } else { - // Manually parse the request body if not already parsed - let body = ''; - req.on('data', (chunk) => { - body += chunk.toString(); - }); - req.on('end', () => { - if (body) { - try { - const parsedBody = JSON.parse(body); - console.log(chalk.magenta('Body:'), parsedBody); - } catch (error) { - console.log(chalk.red('Failed to parse body'), error); - } - } - next(); - }); - return; - } - - next(); -}; +// hello.js +module.exports = (req, res, next) => { + res.header('X-Hello', 'World') + next() +} ``` -This will output: - -```sh -Index: -http://localhost:3000/ - -Static files: -Serving ./public directory if it exists - -Endpoints: -http://localhost:3000/posts -http://localhost:3000/comments -http://localhost:3000/profile - -PATCH /posts/1 2025-01-03T08:25:13.138Z -Body: { title: 'foo', body: 'bar', userId: 1 } -POST /posts 2025-01-03T08:25:18.661Z -Body: { title: 'foo', body: 'bar', userId: 1 } -GET /posts 2025-01-03T08:25:20.159Z +```bash +json-server db.json --middleware ./hello.js +json-server db.json --middleware ./first.js ./second.js ``` ## Notable differences with v0.17 diff --git a/middleware/logger.mjs b/middleware/logger.mjs deleted file mode 100644 index a4a7f2210..000000000 --- a/middleware/logger.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import chalk from 'chalk'; - -export default (req, _res, next) => { - const currentDate = new Date().toISOString(); - console.log(chalk.green(req.method), chalk.yellow(req.url), chalk.blue(`${currentDate}`)); - - // Check if the request body is already parsed - if (req.body && Object.keys(req.body).length > 0) { - console.log(chalk.magenta('Body:'), req.body); - } else { - // Manually parse the request body if not already parsed - let body = ''; - req.on('data', (chunk) => { - body += chunk.toString(); - }); - req.on('end', () => { - if (body) { - try { - const parsedBody = JSON.parse(body); - console.log(chalk.magenta('Body:'), parsedBody); - } catch (error) { - console.log(chalk.red('Failed to parse body'), error); - } - } - next(); - }); - return; - } - - next(); -}; \ No newline at end of file diff --git a/package.json b/package.json index c325f3a4d..a0cfb2993 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "dev": "tsx watch src/bin.ts fixtures/db.json", "build": "rm -rf lib && tsc", "test": "node --import tsx/esm --test src/*.test.ts", - "logger": "tsx watch src/bin.ts fixtures/db.json --middleware=middleware/logger.mjs", "lint": "eslint src", "prepare": "husky", "prepublishOnly": "npm run build" @@ -61,4 +60,4 @@ "sirv": "^2.0.4", "sort-on": "^6.1.0" } -} \ No newline at end of file +} diff --git a/src/app.ts b/src/app.ts index 5eaaa4d6e..3fa601985 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,7 +16,7 @@ const isProduction = process.env['NODE_ENV'] === 'production' export type AppOptions = { logger?: boolean static?: string[] - middleware?: (req: unknown, res: unknown, next: unknown) => void + middlewares?: ((req: unknown, res: unknown, next: unknown) => void)[] } const eta = new Eta({ @@ -38,9 +38,7 @@ export function createApp(db: Low, options: AppOptions = {}) { .forEach((dir) => app.use(sirv(dir, { dev: !isProduction }))) // Use middleware if specified - if (options.middleware) { - app.use(options.middleware) - } + options.middlewares?.forEach(m => app.use(m)); // CORS app diff --git a/src/bin.ts b/src/bin.ts index d128b73ae..2100c6657 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -22,7 +22,7 @@ Options: -p, --port Port (default: 3000) -h, --host Host (default: localhost) -s, --static Static files directory (multiple allowed) - --middleware Middleware file + --middleware, -m Paths to middleware files (multiple allowed) --help Show this message --version Show version number `) @@ -57,7 +57,9 @@ function args(): { }, middleware: { type: 'string', - default: '', + short: 'm', + multiple: true, + default: [] }, help: { type: 'boolean', @@ -119,25 +121,27 @@ function args(): { } } -// Load middleware -async function loadMiddleware(middlewarePath: string) { - const resolvedPath = resolve(process.cwd(), middlewarePath) - if (existsSync(resolvedPath)) { - const middlewareModule = await import(resolvedPath) - return middlewareModule.default || middlewareModule - } else { - console.error(`Middleware file not found: ${resolvedPath}`) - process.exit(1) - } -} - -const { file, port, host, static: staticArr, middleware } = args() +const { file, port, host, static: staticArr, middleware: middlewarePaths } = args() if (!existsSync(file)) { console.log(chalk.red(`File ${file} not found`)) process.exit(1) } +// Load middlewares if specified +const middlewareFunctions = await Promise.all( + middlewarePaths.map(async p => { + if (!existsSync(p)){ + console.error(`Middleware file not found: ${resolvedPath}`) + return process.exit(1) + } + console.log(chalk.gray(`Loading middleware from ${middlewarePaths}`)) + const resolvedPath = resolve(process.cwd(), p) + const middlewareModule = await import(resolvedPath) + return middlewareModule.default || middlewareModule + }) +); + // Handle empty string JSON file if (readFileSync(file, 'utf-8').trim() === '') { writeFileSync(file, '{}') @@ -158,18 +162,11 @@ const observer = new Observer(adapter) const db = new Low(observer, {}) await db.read() -// Load middleware if specified -let middlewareFunction -if (middleware) { - console.log(chalk.gray(`Loading middleware from ${middleware}`)) - middlewareFunction = await loadMiddleware(middleware) -} - // Create app const app = createApp(db, { logger: false, static: staticArr, - middleware: middlewareFunction, + middlewares: middlewareFunctions, }) function logRoutes(data: Data) { diff --git a/test.http b/test.http deleted file mode 100644 index 78fed90f9..000000000 --- a/test.http +++ /dev/null @@ -1,22 +0,0 @@ -### -GET http://localhost:3000/posts - -### -POST http://localhost:3000/posts -Content-Type: application/json - -{ - "title": "foo", - "body": "bar", - "userId": 1 -} - -### -PATCH http://localhost:3000/posts/1 -Content-Type: application/json - -{ - "title": "foo", - "body": "bar", - "userId": 1 -} \ No newline at end of file From dc94ce42843b1186ea49434cc408c00246f63de2 Mon Sep 17 00:00:00 2001 From: Sequencer <45060278+sequencerr@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:02:46 +0400 Subject: [PATCH 5/7] Rm additional console.log --- src/bin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin.ts b/src/bin.ts index 2100c6657..5dcdd1c2d 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -184,7 +184,6 @@ function logRoutes(data: Data) { ) .join('\n'), ) - console.log() } const kaomojis = ['♡⸜(˶˃ ᵕ ˂˶)⸝♡', '♡( ◡‿◡ )', '( ˶ˆ ᗜ ˆ˵ )', '(˶ᵔ ᵕ ᵔ˶)'] From d6abc236266892adc4ebb4a4ebc945a6fd24110b Mon Sep 17 00:00:00 2001 From: Sequencer <45060278+sequencerr@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:05:07 +0400 Subject: [PATCH 6/7] Upd make follow prev typing as casting --- src/bin.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 5dcdd1c2d..f2c5e4768 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -105,10 +105,10 @@ function args(): { // App args and options return { file: positionals[0] ?? '', - port: parseInt(values.port), - host: values.host, - static: values.static, - middleware: values.middleware, + port: parseInt(values.port as string), + host: values.host as string, + static: values.static as string[], + middleware: values.middleware as string[], } } catch (e) { if ((e as NodeJS.ErrnoException).code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') { From 333f4a3ce5e597004dfc09032dda35c8110a4012 Mon Sep 17 00:00:00 2001 From: Sequencer <45060278+sequencerr@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:28:01 +0400 Subject: [PATCH 7/7] Fix middleware loader --- src/bin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index f2c5e4768..c7b81e313 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -131,12 +131,12 @@ if (!existsSync(file)) { // Load middlewares if specified const middlewareFunctions = await Promise.all( middlewarePaths.map(async p => { - if (!existsSync(p)){ + const resolvedPath = resolve(process.cwd(), p) + if (!existsSync(resolvedPath)){ console.error(`Middleware file not found: ${resolvedPath}`) return process.exit(1) } - console.log(chalk.gray(`Loading middleware from ${middlewarePaths}`)) - const resolvedPath = resolve(process.cwd(), p) + console.log(chalk.gray(`Loading middleware from ${resolvedPath}`)) const middlewareModule = await import(resolvedPath) return middlewareModule.default || middlewareModule })