From f1c724dba619abaae0f91a859c99f0648d11ef7d Mon Sep 17 00:00:00 2001 From: "remi.kristelijn" Date: Fri, 3 Jan 2025 09:28:21 +0100 Subject: [PATCH 1/3] 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/3] 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/3] 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}`));