{"id":13459095,"url":"https://github.com/koajs/joi-router","last_synced_at":"2025-05-15T14:06:31.106Z","repository":{"id":19454348,"uuid":"22698679","full_name":"koajs/joi-router","owner":"koajs","description":"Configurable, input and output validated routing for koa","archived":false,"fork":false,"pushed_at":"2024-09-15T23:53:49.000Z","size":470,"stargazers_count":450,"open_issues_count":9,"forks_count":96,"subscribers_count":51,"default_branch":"master","last_synced_at":"2025-04-29T06:28:29.614Z","etag":null,"topics":["javascript","joi","koa","router"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/koajs.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-08-06T21:26:37.000Z","updated_at":"2024-08-12T10:51:02.000Z","dependencies_parsed_at":"2024-01-04T10:04:51.704Z","dependency_job_id":"61e6abe9-4003-4dad-aaf0-cf0394aafe33","html_url":"https://github.com/koajs/joi-router","commit_stats":{"total_commits":172,"total_committers":29,"mean_commits":5.931034482758621,"dds":0.2790697674418605,"last_synced_commit":"d4e209d68e6109cd8e18684eef94040f025095cd"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koajs%2Fjoi-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koajs%2Fjoi-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koajs%2Fjoi-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koajs%2Fjoi-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/koajs","download_url":"https://codeload.github.com/koajs/joi-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252984670,"owners_count":21835834,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","joi","koa","router"],"created_at":"2024-07-31T09:01:03.681Z","updated_at":"2025-05-15T14:06:26.011Z","avatar_url":"https://github.com/koajs.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Middleware","目录","仓库"],"sub_categories":["中间件"],"readme":"# joi-router\n\nEasy, rich and fully validated [koa][] routing.\n\n[![NPM version][npm-image]][npm-url]\n[![build status][travis-image]][travis-url]\n[![Test coverage][codecov-image]][codecov-url]\n[![David deps][david-image]][david-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/koa-joi-router.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/koa-joi-router\n[travis-image]: https://img.shields.io/travis/koajs/joi-router.svg?style=flat-square\n[travis-url]: https://travis-ci.org/koajs/joi-router\n[codecov-image]: https://codecov.io/github/koajs/joi-router/coverage.svg?branch=master\n[codecov-url]: https://codecov.io/github/koajs/joi-router?branch=master\n[david-image]: https://img.shields.io/david/koajs/joi-router.svg?style=flat-square\n[david-url]: https://david-dm.org/koajs/joi-router\n[download-image]: https://img.shields.io/npm/dm/koa-joi-router.svg?style=flat-square\n[download-url]: https://npmjs.org/package/koa-joi-router\n[co]: https://github.com/tj/co\n[koa]: http://koajs.com\n[co-body]: https://github.com/visionmedia/co-body\n[await-busboy]: https://github.com/aheckmann/await-busboy\n[joi]: https://github.com/hapijs/joi\n[@koa/router]: https://github.com/koajs/router\n[generate API documentation]: https://github.com/a-s-o/koa-docs\n[path-to-regexp]: https://github.com/pillarjs/path-to-regexp\n\n#### Features:\n\n- built in input validation using [joi][]\n- built in [output validation](#validating-output) using [joi][]\n- built in body parsing using [co-body][] and [await-busboy][]\n- built on the great [@koa/router][]\n- [exposed route definitions](#routes) for later analysis\n- string path support\n- [regexp-like path support](#path-regexps)\n- [multiple method support](#multiple-methods-support)\n- [multiple middleware support](#multiple-middleware-support)\n- [continue on error support](#handling-errors)\n- [router prefixing support](#prefix)\n- [router level middleware support](#use)\n- meta data support\n- HTTP 405 and 501 support\n\n#### Node compatibility\n\nNodeJS `\u003e= 12` is required.\n\n#### Example\n\n```js\nconst koa = require('koa');\nconst router = require('koa-joi-router');\nconst Joi = router.Joi;\n\nconst public = router();\n\npublic.get('/', async (ctx) =\u003e {\n  ctx.body = 'hello joi-router!';\n});\n\npublic.route({\n  method: 'post',\n  path: '/signup',\n  validate: {\n    body: {\n      name: Joi.string().max(100),\n      email: Joi.string().lowercase().email(),\n      password: Joi.string().max(100),\n      _csrf: Joi.string().token()\n    },\n    type: 'form',\n    output: {\n      200: {\n        body: {\n          userId: Joi.string(),\n          name: Joi.string()\n        }\n      }\n    }\n  },\n  handler: async (ctx) =\u003e {\n    const user = await createUser(ctx.request.body);\n    ctx.status = 201;\n    ctx.body = user;\n  }\n});\n\nconst app = new koa();\napp.use(public.middleware());\napp.listen(3000);\n```\n\n## Usage\n`koa-joi-router` returns a constructor which you use to define your routes.\nThe design is such that you construct multiple router instances, one for\neach section of your application which you then add as koa middleware.\n\n```js\nconst Koa = require(\"koa\")\nconst router = require('koa-joi-router');\n\nconst pub = router();\nconst admin = router();\nconst auth = router();\n\n// add some routes ..\npub.get('/some/path', async () =\u003e {});\nadmin.get('/admin', async () =\u003e {});\nauth.post('/auth', async () =\u003e {});\n\nconst app = new Koa();\napp.use(pub.middleware());\napp.use(admin.middleware());\napp.use(auth.middleware());\napp.listen();\n```\n\n## Module properties\n\n### .Joi\n\nIt is **HIGHLY RECOMMENDED** you use this bundled version of Joi\nto avoid bugs related to passing an object created with a different\nrelease of Joi into the router.\n\n```js\nconst koa = require('koa');\nconst router = require('koa-joi-router');\nconst Joi = router.Joi;\n```\n\n## Router instance methods\n\n### .route()\n\nAdds a new route to the router. `route()` accepts an object or array of objects\ndescribing route behavior.\n\n```js\nconst router = require('koa-joi-router');\nconst public = router();\n\npublic.route({\n  method: 'post',\n  path: '/signup',\n  validate: {\n    header: joiObject,\n    query: joiObject,\n    params: joiObject,\n    body: joiObject,\n    maxBody: '64kb',\n    output: { '400-600': { body: joiObject } },\n    type: 'form',\n    failure: 400,\n    continueOnError: false\n  },\n  pre: async (ctx, next) =\u003e {\n    await checkAuth(ctx);\n    return next();\n  },\n  handler: async (ctx) =\u003e {\n    await createUser(ctx.request.body);\n    ctx.status = 201;\n  },\n  meta: { 'this': { is: 'stored internally with the route definition' }}\n});\n```\n\nor\n\n```js\nconst router = require('koa-joi-router');\nconst public = router();\n\nconst routes = [\n  {\n    method: 'post',\n    path: '/users',\n    handler: async (ctx) =\u003e {}\n  },\n  {\n    method: 'get',\n    path: '/users',\n    handler: async (ctx) =\u003e {}\n  }\n];\n\npublic.route(routes);\n```\n\n##### .route() options\n\n- `method`: **required** HTTP method like \"get\", \"post\", \"put\", etc\n- `path`: **required** string\n- `validate`\n  - `header`: object which conforms to [Joi][] validation\n  - `query`: object which conforms to [Joi][] validation\n  - `params`: object which conforms to [Joi][] validation\n  - `body`: object which conforms to [Joi][] validation\n  - `maxBody`: max incoming body size for forms or json input\n  - `failure`: HTTP response code to use when input validation fails. default `400`\n  - `type`: if validating the request body, this is **required**. either `form`, `json` or `multipart`\n  - `formOptions`: options for co-body form parsing when `type: 'form'`\n  - `jsonOptions`: options for co-body json parsing when `type: 'json'`\n  - `multipartOptions`: options for [busboy][] parsing when `type: 'multipart'`\n     - [any busboy constructor option][busboy]. eg `{ limits: { files: 1 }}`\n     - `autoFields`: Determines whether form fields should be auto-parsed (default: `true`). See the [await-busboy docs](https://github.com/aheckmann/await-busboy#parts--parsestream-options).\n  - `output`: see [output validation](#validating-output)\n  - `continueOnError`: if validation fails, this flags determines if `koa-joi-router` should [continue processing](#handling-errors) the middleware stack or stop and respond with an error immediately. useful when you want your route to handle the error response. default `false`\n  - `validateOptions`: options for Joi validate. default `{}`\n- `handler`: **required** async function or functions\n- `pre`: async function or function, will be called before parser and validators\n- `meta`: meta data about this route. `koa-joi-router` ignores this but stores it along with all other route data\n\n### .get(),post(),put(),delete() etc - HTTP methods\n\n`koa-joi-router` supports the traditional `router.get()`, `router.post()` type APIs\nas well.\n\n```js\nconst router = require('koa-joi-router');\nconst admin = router();\n\n// signature: router.method(path [, config], handler [, handler])\n\nadmin.put('/thing', handler);\nadmin.get('/thing', middleware, handler);\nadmin.post('/thing', config, handler);\nadmin.delete('/thing', config, middleware, handler);\n```\n\n### .use()\nMiddleware run in the order they are defined by .use()(or .get(), etc.) They are invoked sequentially, requests start at the first middleware and work their way \"down\" the middleware stack which matches Express 4 API.\n\n```js\nconst router = require('koa-joi-router');\nconst users = router();\n\nusers.get('/:id', handler);\nusers.use('/:id', runThisAfterHandler);\n```\n\n### .prefix()\n\nDefines a route prefix for all defined routes. This is handy in \"mounting\" scenarios.\n\n```js\nconst router = require('koa-joi-router');\nconst users = router();\n\nusers.get('/:id', handler);\n// GET /users/3 -\u003e 404\n// GET /3 -\u003e 200\n\nusers.prefix('/users');\n// GET /users/3 -\u003e 200\n// GET /3 -\u003e 404\n```\n\n### .param()\n\nDefines middleware for named route parameters. Useful for auto-loading or validation.\n\n_See [@koa/router](https://github.com/koajs/router/blob/master/API.md#module_koa-router--Router+param)_\n\n```js\nconst router = require('koa-joi-router');\nconst users = router();\n\nconst findUser = (id) =\u003e {\n  // stub\n  return Promise.resolve('Cheddar');\n};\n\nusers.param('user', async (id, ctx, next) =\u003e {\n  const user = await findUser(id);\n  if (!user) return ctx.status = 404;\n  ctx.user = user;\n  await next();\n});\n\nusers.get('/users/:user', (ctx) =\u003e {\n  ctx.body = `Hello ${ctx.user}`;\n});\n\n// GET /users/3 -\u003e 'Hello Cheddar'\n```\n\n### .middleware()\n\nGenerates routing middleware to be used with `koa`. If this middleware is\nnever added to your `koa` application, your routes will not work.\n\n```js\nconst router = require('koa-joi-router');\nconst public = router();\n\npublic.get('/home', homepage);\n\nconst app = koa();\napp.use(public.middleware()); // wired up\napp.listen();\n```\n\n## Additions to ctx.state\n\nThe route definition for the currently matched route is available\nvia `ctx.state.route`. This object is not the exact same route\ndefinition object which was passed into koa-joi-router, nor is it\nused internally - any changes made to this object will\nnot have an affect on your running application but is available\nto meet your introspection needs.\n\n```js\nconst router = require('koa-joi-router');\nconst public = router();\npublic.get('/hello', async (ctx) =\u003e {\n  console.log(ctx.state.route);\n});\n```\n\n## Additions to ctx.request\n\nWhen using the `validate.type` option, `koa-joi-router` adds a few new properties\nto `ctx.request` to faciliate input validation.\n\n### ctx.request.body\n\nThe `ctx.request.body` property will be set when either of the following\n`validate.type`s are set:\n\n- json\n- form\n\n#### json\n\nWhen `validate.type` is set to `json`, the incoming data must be JSON. If it is not,\nvalidation will fail and the response status will be set to 400 or the value of\n`validate.failure` if specified. If successful, `ctx.request.body` will be set to the\nparsed request input.\n\n```js\nadmin.route({\n  method: 'post',\n  path: '/blog',\n  validate: { type: 'json' },\n  handler: async (ctx) =\u003e {\n    console.log(ctx.request.body); // the incoming json as an object\n  }\n});\n```\n\n#### form\n\nWhen `validate.type` is set to `form`, the incoming data must be form data\n(x-www-form-urlencoded). If it is not, validation will fail and the response\nstatus will be set to 400 or the value of `validate.failure` if specified.\nIf successful, `ctx.request.body` will be set to the parsed request input.\n\n```js\nadmin.route({\n  method: 'post',\n  path: '/blog',\n  validate: { type: 'form' },\n  handler: async (ctx) =\u003e {\n    console.log(ctx.request.body) // the incoming form as an object\n  }\n});\n```\n\n### ctx.request.parts\n\nThe `ctx.request.parts` property will be set when either of the following\n`validate.type`s are set:\n\n- multipart\n\n#### multipart\n\nWhen `validate.type` is set to `multipart`, the incoming data must be multipart data.\nIf it is not, validation will fail and the response\nstatus will be set to 400 or the value of `validate.failure` if specified.\nIf successful, `ctx.request.parts` will be set to an\n[await-busboy][] object.\n\n```js\nadmin.route({\n  method: 'post',\n  path: '/blog',\n  validate: { type: 'multipart' },\n  handler: async (ctx) =\u003e {\n    const parts = ctx.request.parts;\n    let part;\n\n    try {\n      while ((part = await parts)) {\n        // do something with the incoming part stream\n        part.pipe(someOtherStream);\n      }\n    } catch (err) {\n      // handle the error\n    }\n\n    console.log(parts.field.name); // form data\n  }\n});\n```\n\n## Handling non-validated input\n\n_Note:_ if you do not specify a value for `validate.type`, the\nincoming payload will not be parsed or validated. It is up to you to\nparse the incoming data however you see fit.\n\n```js\nadmin.route({\n  method: 'post',\n  path: '/blog',\n  validate: { },\n  handler: async (ctx) =\u003e {\n    console.log(ctx.request.body, ctx.request.parts); // undefined undefined\n  }\n})\n```\n\n## Validating output\n\nValidating the output body and/or headers your service generates on a\nper-status-code basis is supported. This comes in handy when contracts\nbetween your API and client are strict e.g. any change in response\nschema could break your downstream clients. In a very active codebase, this\nfeature buys you stability. If the output is invalid, an HTTP status 500\nwill be used.\n\nLet's look at some examples:\n\n### Validation of an individual status code\n\n```js\nrouter.route({\n  method: 'post',\n  path: '/user',\n  validate: {\n    output: {\n      200: { // individual status code\n        body: {\n          userId: Joi.string(),\n          name: Joi.string()\n        }\n      }\n    }\n  },\n  handler: handler\n});\n```\n\n### Validation of multiple individual status codes\n\n```js\nrouter.route({\n  method: 'post',\n  path: '/user',\n  validate: {\n    output: {\n      '200,201': { // multiple individual status codes\n        body: {\n          userId: Joi.string(),\n          name: Joi.string()\n        }\n      }\n    }\n  },\n  handler: handler\n});\n```\n\n### Validation of a status code range\n\n```js\nrouter.route({\n  method: 'post',\n  path: '/user',\n  validate: {\n    output: {\n      '200-299': { // status code range\n        body: {\n          userId: Joi.string(),\n          name: Joi.string()\n        }\n      }\n    }\n  },\n  handler: handler\n});\n```\n\n### Validation of multiple individual status codes and ranges combined\n\nYou are free to mix and match ranges and individual status codes.\n\n```js\nrouter.route({\n  method: 'post',\n  path: '/user',\n  validate: {\n    output: {\n      '200,201,300-600': { // mix it up\n        body: {\n          userId: Joi.string(),\n          name: Joi.string()\n        }\n      }\n    }\n  },\n  handler: handler\n});\n```\n\n### Validation of output headers\n\nValidating your output headers is also supported via the `headers` property:\n\n```js\nrouter.route({\n  method: 'post',\n  path: '/user',\n  validate: {\n    output: {\n      '200,201': {\n        body: {\n          userId: Joi.string(),\n          name: Joi.string()\n        },\n        headers: Joi.object({ // validate headers too\n          authorization: Joi.string().required()\n        }).options({\n          allowUnknown: true\n        })\n      },\n      '500-600': {\n        body: { // this rule only runs when a status 500 - 600 is used\n          error_code: Joi.number(),\n          error_msg: Joi.string()\n        }\n      }\n    }\n  },\n  handler: handler\n});\n```\n\n## Router instance properties\n\n### .routes\n\nEach router exposes it's route definitions through it's `routes` property.\nThis is helpful when you'd like to introspect the previous definitions and\ntake action e.g. to [generate API documentation][] etc.\n\n```js\nconst router = require('koa-joi-router');\nconst admin = router();\nadmin.post('/thing', { validate: { type: 'multipart' }}, handler);\n\nconsole.log(admin.routes);\n// [ { path: '/thing',\n//     method: [ 'post' ],\n//     handler: [ [Function] ],\n//     validate: { type: 'multipart' } } ]\n```\n\n## Path RegExps\n\nSometimes you need `RegExp`-like syntax support for your route definitions.\nBecause [path-to-regexp][]\nsupports it, so do we!\n\n```js\nconst router = require('koa-joi-router');\nconst admin = router();\nadmin.get('/blog/:year(\\\\d{4})-:day(\\\\d{2})-:article(\\\\d{3})', async (ctx, next) =\u003e { \n console.log(ctx.request.params) // { year: '2017', day: '01', article: '011' } \n});\n```\n\n## Multiple methods support\n\nDefining a route for multiple HTTP methods in a single shot is supported.\n\n```js\nconst router = require('koa-joi-router');\nconst admin = router();\nadmin.route({\n  path: '/',\n  method: ['POST', 'PUT'],\n  handler: fn\n});\n```\n\n## Multiple middleware support\n\nOften times you may need to add additional, route specific middleware to a\nsingle route.\n\n```js\nconst router = require('koa-joi-router');\nconst admin = router();\nadmin.route({\n  path: '/',\n  method: ['POST', 'PUT'],\n  handler: [ yourMiddleware, yourHandler ]\n});\n```\n\n## Nested middleware support\n\nYou may want to bundle and nest middleware in different ways for reuse and\norganization purposes.\n\n```js\nconst router = require('koa-joi-router');\nconst admin = router();\nconst commonMiddleware = [ yourMiddleware, someOtherMiddleware ];\nadmin.route({\n  path: '/',\n  method: ['POST', 'PUT'],\n  handler: [ commonMiddleware, yourHandler ]\n});\n```\n\nThis also works with the .get(),post(),put(),delete(), etc HTTP method helpers.\n\n```js\nconst router = require('koa-joi-router');\nconst admin = router();\nconst commonMiddleware = [ yourMiddleware, someOtherMiddleware ];\nadmin.get('/', commonMiddleware, yourHandler);\n```\n\n## Handling errors\n\nBy default, `koa-joi-router` stops processing the middleware stack when either\ninput validation fails. This means your route will not be reached. If\nthis isn't what you want, for example, if you're writing a web app which needs\nto respond with custom html describing the errors, set the `validate.continueOnError`\nflag to true. You can find out if validation failed by checking `ctx.invalid`.\n\n```js\nadmin.route({\n  method: 'post',\n  path: '/add',\n  validate: {\n    type: 'form',\n    body: {\n      id: Joi.string().length(10)\n    },\n    continueOnError: true\n  },\n  handler: async (ctx) =\u003e {\n    if (ctx.invalid) {\n      console.log(ctx.invalid.header);\n      console.log(ctx.invalid.query);\n      console.log(ctx.invalid.params);\n      console.log(ctx.invalid.body);\n      console.log(ctx.invalid.type);\n    }\n\n    ctx.body = await render('add', { errors: ctx.invalid });\n  }\n});\n```\n\n## Development\n\n### Running tests\n\n- `npm test` runs tests + code coverage + lint\n- `npm run lint` runs lint only\n- `npm run lint-fix` runs lint and attempts to fix syntax issues\n- `npm run test-cov` runs tests + test coverage\n- `npm run open-cov` opens test coverage results in your browser\n- `npm run test-only` runs tests only\n\n## LICENSE\n\n[MIT](https://github.com/koajs/joi-router/blob/master/LICENSE)\n\n[busboy]: https://github.com/mscdex/busboy#busboy-methods\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoajs%2Fjoi-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkoajs%2Fjoi-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoajs%2Fjoi-router/lists"}