diff --git a/README.md b/README.md index 4cfc29b..d8db6ed 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,14 @@ const router = new Router() // Enabling buildin CORS support router.cors() +// Register global middleware +router.use((req, res, next) => { + res.headers.set('X-Global-Middlewares', 'true') + next() +}) + // Simple get router.get('/user', (req, res) => { - res.body = { data: { id: 1, @@ -89,6 +94,15 @@ Enable or disable debug mode. Which will return the `error.stack` in case of an State is a `boolean` which determines if debug mode should be enabled or not (default: `true`) +### `router.use(handler)` + +Register a global middleware handler. + +#### `handler` (function) + +Handler is a `function` which will be called for every request. + + ### `router.cors([config])` If enabled will overwrite other `OPTIONS` requests. @@ -130,18 +144,17 @@ An unlimited number of functions getting [`req`](#req-object) and [`res`](#res-o Key | Type | Description --------- | ------------------- | ----------- `body` | `object` / `string` | Only available if method is `POST`, `PUT`, `PATCH` or `DELETE`. Contains either the received body string or a parsed object if valid JSON was sent. -`headers` | `object` | Object containing request headers +`headers` | `Headers` | Request [Headers Object](https://developer.mozilla.org/en-US/docs/Web/API/Headers) `method` | `string` | HTTP request method `params` | `object` | Object containing all parameters defined in the url string `query` | `object` | Object containing all query parameters - ### `res`-Object Key | Type | Description ----------- | ------------------- | ----------- `body` | `object` / `string` | Either set an `object` (will be converted to JSON) or a string -`headers` | `object` | Object you can set response headers in +`headers` | `Headers` | Response [Headers Object](https://developer.mozilla.org/en-US/docs/Web/API/Headers) `status` | `integer` | Return status code (default: `204`) `webSocket` | `WebSocket` | Upgraded websocket connection diff --git a/index.d.ts b/index.d.ts index 3398849..f97b116 100644 --- a/index.d.ts +++ b/index.d.ts @@ -14,13 +14,20 @@ declare class Router { * @type {Route[]} */ protected routes: Route[] + /** + * Global Handlers + * + * @protected + * @type {Handler[]} + */ + protected globalHandlers: Handler[] /** * Debug Mode * * @protected * @type {boolean} */ - protected debugMode: boolean + protected debugMode: boolean = false /** * CORS Config * @@ -43,7 +50,7 @@ declare class Router { * @property {string} method HTTP request method * @property {Object} params Object containing all parameters defined in the url string * @property {Object} query Object containing all query parameters - * @property {Object} headers Object containing request headers + * @property {Headers} headers Request headers object * @property {Object | string} body Only available if method is `POST`, `PUT`, `PATCH` or `DELETE`. Contains either the received body string or a parsed object if valid JSON was sent. * @property {Object} cf object containing custom Cloudflare properties. (https://developers.cloudflare.com/workers/examples/accessing-the-cloudflare-object) */ @@ -51,7 +58,7 @@ declare class Router { * Response Object * * @typedef RouterResponse - * @property {Object} headers Object you can set response headers in + * @property {Headers} headers Response headers object * @property {number} status Return status code (default: `204`) * @property {Object | string} body Either an `object` (will be converted to JSON) or a string * @property {Response} raw A response object that is to be returned, this will void all other res properties and return this as is. @@ -70,6 +77,13 @@ declare class Router { * @param {Response} response * @param {next} next */ + /** + * Register global handler + * + * @param {RouterHandler} handler + * @param handlers + */ + use(handler: RouterHandler): Router /** * Register CONNECT route * @@ -172,9 +186,9 @@ declare class Router { /** * Debug Mode * - * @param {boolean} state Whether to turn on or off debug mode (default: true) + * @param {boolean} [state=true] Whether to turn on or off debug mode (default: true) */ - debug(state: boolean): void + debug(state?: boolean): void /** * Enable CORS support * @@ -204,9 +218,10 @@ declare class Router { * Handle requests * * @param {Request} request + * @param {any=} extend * @returns {Response} */ - handle(request: Request): Response + handle(request: Request, extend?: any): Response } declare namespace Router { export { Route, RouterRequest, RouterResponse, RouterNext, RouterHandler, RouterCorsConfig } @@ -265,16 +280,20 @@ type RouterRequest = { * HTTP request method */ method: string + /** + * Object containing request headers + */ + headers: Headers + /** + * URL String + */ + url: string /** * Object containing all parameters defined in the url string */ params: { [key: string]: string } - /** - * Object containing request headers - */ - headers: Headers /** * Only available if method is `POST`, `PUT` or `PATCH`. Contains either the received body string or a parsed object if valid JSON was sent. */ diff --git a/index.js b/index.js index 6c6ea9b..3a66caf 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,11 @@ class Router { */ this.routes = [] + /** + * Global Handlers + */ + this.globalHandlers = [] + /** * Debug Mode * @@ -49,7 +54,7 @@ class Router { * @property {string} method HTTP request method * @property {Object} params Object containing all parameters defined in the url string * @property {Object} query Object containing all query parameters - * @property {Object} headers Object containing request headers + * @property {Headers} headers Request headers object * @property {Object | string} body Only available if method is `POST`, `PUT`, `PATCH` or `DELETE`. Contains either the received body string or a parsed object if valid JSON was sent. * @property {Object} cf object containing custom Cloudflare properties. (https://developers.cloudflare.com/workers/examples/accessing-the-cloudflare-object) */ @@ -58,7 +63,7 @@ class Router { * Response Object * * @typedef RouterResponse - * @property {Object} headers Object you can set response headers in + * @property {Headers} headers Response headers object * @property {number} status Return status code (default: `204`) * @property {Object | string} body Either an `object` (will be converted to JSON) or a string * @property {Response} raw A response object that is to be returned, this will void all other res properties and return this as is. @@ -75,11 +80,21 @@ class Router { * Handler Function * * @callback RouterHandler - * @param {Request} request - * @param {Response} response + * @param {RouterRequest} request + * @param {RouterResponse} response * @param {RouterNext} next */ + /** + * Register global handler + * + * @param {RouterHandler} handler + */ + use(handlers) { + this.globalHandlers.push(handlers) + return this + } + /** * Register CONNECT route * @@ -206,7 +221,7 @@ class Router { /** * Debug Mode * - * @param {boolean} state Whether to turn on or off debug mode (default: true) + * @param {boolean} [state=true] Whether to turn on or off debug mode (default: true) */ debug(state = true) { this.debugMode = state @@ -294,16 +309,24 @@ class Router { * Handle requests * * @param {Request} request + * @param {any=} extend * @returns {Response} */ - async handle(request) { + async handle(request, extend = {}) { try { if (request instanceof Event) { request = request.request console.warn("Warning: Using `event` on `router.handle()` is deprecated and might go away in future versions, please use `event.request` instead.") } - const req = { headers: request.headers, method: request.method, url: request.url, cf: request.cf || {} } - req.params = [] + const req = { + ...extend, + method: request.method, + headers: request.headers, + url: request.url, + params: [], + query: {}, + body: '' + } if (req.method === 'OPTIONS' && Object.keys(this.corsConfig).length) { return new Response(null, { headers: { @@ -315,7 +338,7 @@ class Router { status: this.corsConfig.optionsSuccessStatus }) } - if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) { + if (['POST', 'PUT', 'PATCH'].includes(req.method)) { if (req.headers.has('Content-Type') && req.headers.get('Content-Type').includes('json')) { try { req.body = await request.json() @@ -336,28 +359,26 @@ class Router { status: 404 }) } - const res = { headers: {} } + const res = { headers: new Headers() } if (Object.keys(this.corsConfig).length) { - res.headers = { - ...res.headers, - 'Access-Control-Allow-Origin': this.corsConfig.allowOrigin, - 'Access-Control-Allow-Methods': this.corsConfig.allowMethods, - 'Access-Control-Allow-Headers': this.corsConfig.allowHeaders, - 'Access-Control-Max-Age': this.corsConfig.maxAge, - } + res.headers.set('Access-Control-Allow-Origin', this.corsConfig.allowOrigin) + res.headers.set('Access-Control-Allow-Methods', this.corsConfig.allowMethods) + res.headers.set('Access-Control-Allow-Headers', this.corsConfig.allowHeaders) + res.headers.set('Access-Control-Max-Age', this.corsConfig.maxAge) } + const handlers = [...this.globalHandlers, ...route.handlers] let prevIndex = -1 const runner = async index => { if (index === prevIndex) throw new Error('next() called multiple times') prevIndex = index - if (typeof route.handlers[index] === 'function') - await route.handlers[index](req, res, async () => await runner(index + 1)) + if (typeof handlers[index] === 'function') + await handlers[index](req, res, async () => await runner(index + 1)) } await runner(0) if (typeof res.body === 'object') { - if (!res.headers['Content-Type']) - res.headers['Content-Type'] = 'application/json' + if (!res.headers.has('Content-Type')) + res.headers.set('Content-Type', 'application/json') res.body = JSON.stringify(res.body) } if (res.raw) { diff --git a/package.json b/package.json index e507265..599ba48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tsndr/cloudflare-worker-router", - "version": "1.1.11", + "version": "1.3.1", "description": "", "main": "index.js", "scripts": {},