Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add middleware #1625

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions middleware/logger.mjs
Original file line number Diff line number Diff line change
@@ -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();
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -60,4 +61,4 @@
"sirv": "^2.0.4",
"sort-on": "^6.1.0"
}
}
}
6 changes: 6 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -36,6 +37,11 @@ export function createApp(db: Low<Data>, 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) {
app.use(options.middleware)
}

// CORS
app
.use((req, res, next) => {
Expand Down
53 changes: 42 additions & 11 deletions src/bin.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -19,11 +19,12 @@ function help() {
console.log(`Usage: json-server [options] <file>

Options:
-p, --port <port> Port (default: 3000)
-h, --host <host> Host (default: localhost)
-s, --static <dir> Static files directory (multiple allowed)
--help Show this message
--version Show version number
-p, --port <port> Port (default: 3000)
-h, --host <host> Host (default: localhost)
-s, --static <dir> Static files directory (multiple allowed)
--middleware <file> Middleware file
--help Show this message
--version Show version number
`)
}

Expand All @@ -33,6 +34,7 @@ function args(): {
port: number
host: string
static: string[]
middleware: string
} {
try {
const { values, positionals } = parseArgs({
Expand All @@ -53,6 +55,10 @@ function args(): {
multiple: true,
default: [],
},
middleware: {
type: 'string',
default: '',
},
help: {
type: 'boolean',
},
Expand Down Expand Up @@ -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') {
Expand All @@ -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`))
Expand All @@ -139,8 +158,19 @@ const observer = new Observer(adapter)
const db = new Low<Data>(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:'))
Expand All @@ -157,6 +187,7 @@ function logRoutes(data: Data) {
)
.join('\n'),
)
console.log()
}

const kaomojis = ['♡⸜(˶˃ ᵕ ˂˶)⸝♡', '♡( ◡‿◡ )', '( ˶ˆ ᗜ ˆ˵ )', '(˶ᵔ ᵕ ᵔ˶)']
Expand Down
22 changes: 22 additions & 0 deletions test.http
Original file line number Diff line number Diff line change
@@ -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
}