Coder Social home page Coder Social logo

joi-router's Introduction

joi-router

Easy, rich and fully validated koa routing.

NPM version build status Test coverage David deps npm download

Features:

Node compatibility

NodeJS >= 12 is required.

Example

const koa = require('koa');
const router = require('koa-joi-router');
const Joi = router.Joi;

const public = router();

public.get('/', async (ctx) => {
  ctx.body = 'hello joi-router!';
});

public.route({
  method: 'post',
  path: '/signup',
  validate: {
    body: {
      name: Joi.string().max(100),
      email: Joi.string().lowercase().email(),
      password: Joi.string().max(100),
      _csrf: Joi.string().token()
    },
    type: 'form',
    output: {
      200: {
        body: {
          userId: Joi.string(),
          name: Joi.string()
        }
      }
    }
  },
  handler: async (ctx) => {
    const user = await createUser(ctx.request.body);
    ctx.status = 201;
    ctx.body = user;
  }
});

const app = new koa();
app.use(public.middleware());
app.listen(3000);

Usage

koa-joi-router returns a constructor which you use to define your routes. The design is such that you construct multiple router instances, one for each section of your application which you then add as koa middleware.

const Koa = require("koa")
const router = require('koa-joi-router');

const pub = router();
const admin = router();
const auth = router();

// add some routes ..
pub.get('/some/path', async () => {});
admin.get('/admin', async () => {});
auth.post('/auth', async () => {});

const app = new Koa();
app.use(pub.middleware());
app.use(admin.middleware());
app.use(auth.middleware());
app.listen();

Module properties

.Joi

It is HIGHLY RECOMMENDED you use this bundled version of Joi to avoid bugs related to passing an object created with a different release of Joi into the router.

const koa = require('koa');
const router = require('koa-joi-router');
const Joi = router.Joi;

Router instance methods

.route()

Adds a new route to the router. route() accepts an object or array of objects describing route behavior.

const router = require('koa-joi-router');
const public = router();

public.route({
  method: 'post',
  path: '/signup',
  validate: {
    header: joiObject,
    query: joiObject,
    params: joiObject,
    body: joiObject,
    maxBody: '64kb',
    output: { '400-600': { body: joiObject } },
    type: 'form',
    failure: 400,
    continueOnError: false
  },
  pre: async (ctx, next) => {
    await checkAuth(ctx);
    return next();
  },
  handler: async (ctx) => {
    await createUser(ctx.request.body);
    ctx.status = 201;
  },
  meta: { 'this': { is: 'stored internally with the route definition' }}
});

or

const router = require('koa-joi-router');
const public = router();

const routes = [
  {
    method: 'post',
    path: '/users',
    handler: async (ctx) => {}
  },
  {
    method: 'get',
    path: '/users',
    handler: async (ctx) => {}
  }
];

public.route(routes);
.route() options
  • method: required HTTP method like "get", "post", "put", etc
  • path: required string
  • validate
    • header: object which conforms to Joi validation
    • query: object which conforms to Joi validation
    • params: object which conforms to Joi validation
    • body: object which conforms to Joi validation
    • maxBody: max incoming body size for forms or json input
    • failure: HTTP response code to use when input validation fails. default 400
    • type: if validating the request body, this is required. either form, json or multipart
    • formOptions: options for co-body form parsing when type: 'form'
    • jsonOptions: options for co-body json parsing when type: 'json'
    • multipartOptions: options for busboy parsing when type: 'multipart'
    • output: see output validation
    • continueOnError: if validation fails, this flags determines if koa-joi-router should continue processing the middleware stack or stop and respond with an error immediately. useful when you want your route to handle the error response. default false
    • validateOptions: options for Joi validate. default {}
  • handler: required async function or functions
  • pre: async function or function, will be called before parser and validators
  • meta: meta data about this route. koa-joi-router ignores this but stores it along with all other route data

.get(),post(),put(),delete() etc - HTTP methods

koa-joi-router supports the traditional router.get(), router.post() type APIs as well.

const router = require('koa-joi-router');
const admin = router();

// signature: router.method(path [, config], handler [, handler])

admin.put('/thing', handler);
admin.get('/thing', middleware, handler);
admin.post('/thing', config, handler);
admin.delete('/thing', config, middleware, handler);

.use()

Middleware 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.

const router = require('koa-joi-router');
const users = router();

users.get('/:id', handler);
users.use('/:id', runThisAfterHandler);

.prefix()

Defines a route prefix for all defined routes. This is handy in "mounting" scenarios.

const router = require('koa-joi-router');
const users = router();

users.get('/:id', handler);
// GET /users/3 -> 404
// GET /3 -> 200

users.prefix('/users');
// GET /users/3 -> 200
// GET /3 -> 404

.param()

Defines middleware for named route parameters. Useful for auto-loading or validation.

See @koa/router

const router = require('koa-joi-router');
const users = router();

const findUser = (id) => {
  // stub
  return Promise.resolve('Cheddar');
};

users.param('user', async (id, ctx, next) => {
  const user = await findUser(id);
  if (!user) return ctx.status = 404;
  ctx.user = user;
  await next();
});

users.get('/users/:user', (ctx) => {
  ctx.body = `Hello ${ctx.user}`;
});

// GET /users/3 -> 'Hello Cheddar'

.middleware()

Generates routing middleware to be used with koa. If this middleware is never added to your koa application, your routes will not work.

const router = require('koa-joi-router');
const public = router();

public.get('/home', homepage);

const app = koa();
app.use(public.middleware()); // wired up
app.listen();

Additions to ctx.state

The route definition for the currently matched route is available via ctx.state.route. This object is not the exact same route definition object which was passed into koa-joi-router, nor is it used internally - any changes made to this object will not have an affect on your running application but is available to meet your introspection needs.

const router = require('koa-joi-router');
const public = router();
public.get('/hello', async (ctx) => {
  console.log(ctx.state.route);
});

Additions to ctx.request

When using the validate.type option, koa-joi-router adds a few new properties to ctx.request to faciliate input validation.

ctx.request.body

The ctx.request.body property will be set when either of the following validate.types are set:

  • json
  • form

json

When validate.type is set to json, the incoming data must be JSON. If it is not, validation will fail and the response status will be set to 400 or the value of validate.failure if specified. If successful, ctx.request.body will be set to the parsed request input.

admin.route({
  method: 'post',
  path: '/blog',
  validate: { type: 'json' },
  handler: async (ctx) => {
    console.log(ctx.request.body); // the incoming json as an object
  }
});

form

When validate.type is set to form, the incoming data must be form data (x-www-form-urlencoded). If it is not, validation will fail and the response status will be set to 400 or the value of validate.failure if specified. If successful, ctx.request.body will be set to the parsed request input.

admin.route({
  method: 'post',
  path: '/blog',
  validate: { type: 'form' },
  handler: async (ctx) => {
    console.log(ctx.request.body) // the incoming form as an object
  }
});

ctx.request.parts

The ctx.request.parts property will be set when either of the following validate.types are set:

  • multipart

multipart

When validate.type is set to multipart, the incoming data must be multipart data. If it is not, validation will fail and the response status will be set to 400 or the value of validate.failure if specified. If successful, ctx.request.parts will be set to an await-busboy object.

admin.route({
  method: 'post',
  path: '/blog',
  validate: { type: 'multipart' },
  handler: async (ctx) => {
    const parts = ctx.request.parts;
    let part;

    try {
      while ((part = await parts)) {
        // do something with the incoming part stream
        part.pipe(someOtherStream);
      }
    } catch (err) {
      // handle the error
    }

    console.log(parts.field.name); // form data
  }
});

Handling non-validated input

Note: if you do not specify a value for validate.type, the incoming payload will not be parsed or validated. It is up to you to parse the incoming data however you see fit.

admin.route({
  method: 'post',
  path: '/blog',
  validate: { },
  handler: async (ctx) => {
    console.log(ctx.request.body, ctx.request.parts); // undefined undefined
  }
})

Validating output

Validating the output body and/or headers your service generates on a per-status-code basis is supported. This comes in handy when contracts between your API and client are strict e.g. any change in response schema could break your downstream clients. In a very active codebase, this feature buys you stability. If the output is invalid, an HTTP status 500 will be used.

Let's look at some examples:

Validation of an individual status code

router.route({
  method: 'post',
  path: '/user',
  validate: {
    output: {
      200: { // individual status code
        body: {
          userId: Joi.string(),
          name: Joi.string()
        }
      }
    }
  },
  handler: handler
});

Validation of multiple individual status codes

router.route({
  method: 'post',
  path: '/user',
  validate: {
    output: {
      '200,201': { // multiple individual status codes
        body: {
          userId: Joi.string(),
          name: Joi.string()
        }
      }
    }
  },
  handler: handler
});

Validation of a status code range

router.route({
  method: 'post',
  path: '/user',
  validate: {
    output: {
      '200-299': { // status code range
        body: {
          userId: Joi.string(),
          name: Joi.string()
        }
      }
    }
  },
  handler: handler
});

Validation of multiple individual status codes and ranges combined

You are free to mix and match ranges and individual status codes.

router.route({
  method: 'post',
  path: '/user',
  validate: {
    output: {
      '200,201,300-600': { // mix it up
        body: {
          userId: Joi.string(),
          name: Joi.string()
        }
      }
    }
  },
  handler: handler
});

Validation of output headers

Validating your output headers is also supported via the headers property:

router.route({
  method: 'post',
  path: '/user',
  validate: {
    output: {
      '200,201': {
        body: {
          userId: Joi.string(),
          name: Joi.string()
        },
        headers: Joi.object({ // validate headers too
          authorization: Joi.string().required()
        }).options({
          allowUnknown: true
        })
      },
      '500-600': {
        body: { // this rule only runs when a status 500 - 600 is used
          error_code: Joi.number(),
          error_msg: Joi.string()
        }
      }
    }
  },
  handler: handler
});

Router instance properties

.routes

Each router exposes it's route definitions through it's routes property. This is helpful when you'd like to introspect the previous definitions and take action e.g. to generate API documentation etc.

const router = require('koa-joi-router');
const admin = router();
admin.post('/thing', { validate: { type: 'multipart' }}, handler);

console.log(admin.routes);
// [ { path: '/thing',
//     method: [ 'post' ],
//     handler: [ [Function] ],
//     validate: { type: 'multipart' } } ]

Path RegExps

Sometimes you need RegExp-like syntax support for your route definitions. Because path-to-regexp supports it, so do we!

const router = require('koa-joi-router');
const admin = router();
admin.get('/blog/:year(\\d{4})-:day(\\d{2})-:article(\\d{3})', async (ctx, next) => { 
 console.log(ctx.request.params) // { year: '2017', day: '01', article: '011' } 
});

Multiple methods support

Defining a route for multiple HTTP methods in a single shot is supported.

const router = require('koa-joi-router');
const admin = router();
admin.route({
  path: '/',
  method: ['POST', 'PUT'],
  handler: fn
});

Multiple middleware support

Often times you may need to add additional, route specific middleware to a single route.

const router = require('koa-joi-router');
const admin = router();
admin.route({
  path: '/',
  method: ['POST', 'PUT'],
  handler: [ yourMiddleware, yourHandler ]
});

Nested middleware support

You may want to bundle and nest middleware in different ways for reuse and organization purposes.

const router = require('koa-joi-router');
const admin = router();
const commonMiddleware = [ yourMiddleware, someOtherMiddleware ];
admin.route({
  path: '/',
  method: ['POST', 'PUT'],
  handler: [ commonMiddleware, yourHandler ]
});

This also works with the .get(),post(),put(),delete(), etc HTTP method helpers.

const router = require('koa-joi-router');
const admin = router();
const commonMiddleware = [ yourMiddleware, someOtherMiddleware ];
admin.get('/', commonMiddleware, yourHandler);

Handling errors

By default, koa-joi-router stops processing the middleware stack when either input validation fails. This means your route will not be reached. If this isn't what you want, for example, if you're writing a web app which needs to respond with custom html describing the errors, set the validate.continueOnError flag to true. You can find out if validation failed by checking ctx.invalid.

admin.route({
  method: 'post',
  path: '/add',
  validate: {
    type: 'form',
    body: {
      id: Joi.string().length(10)
    },
    continueOnError: true
  },
  handler: async (ctx) => {
    if (ctx.invalid) {
      console.log(ctx.invalid.header);
      console.log(ctx.invalid.query);
      console.log(ctx.invalid.params);
      console.log(ctx.invalid.body);
      console.log(ctx.invalid.type);
    }

    ctx.body = await render('add', { errors: ctx.invalid });
  }
});

Development

Running tests

  • npm test runs tests + code coverage + lint
  • npm run lint runs lint only
  • npm run lint-fix runs lint and attempts to fix syntax issues
  • npm run test-cov runs tests + test coverage
  • npm run open-cov opens test coverage results in your browser
  • npm run test-only runs tests only

LICENSE

MIT

joi-router's People

Contributors

3imed-jaberi avatar aheckmann avatar alvarowolfx avatar dependabot[bot] avatar kbrandwijk avatar khalilmansouri avatar kstolp avatar macalinao avatar martinmicunda avatar mdpx avatar nicodinh avatar niftylettuce avatar oprogramador avatar paul42 avatar pixeldrew avatar piyushchauhan2011 avatar pke avatar snyk-bot avatar spmsupun avatar swarthy avatar tony3918 avatar wdanxna avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

joi-router's Issues

Setting converted query values is not working correctly

Joi supports converting/casting types but this feature is not working correctly with the koa integration.

If one has a query validator, this line sets request.query to the converted object but it then gets stringified again here which causes numbers, booleans, etc to be converted to string again.

Update to use newest joi version

Currently date().greater can not be used, cause the bundles joi version is too old.
Could you kindly update the dependencies?

body parser err

const Koa = require('koa');
const router = require('koa-joi-router');
const Joi = require('joi');
const parse = require('co-body');

const routes = router();

routes.route([
  {
    method: 'post',
    path: '/signup',
    validate: {
      body: {
        name: Joi.string().max(100),
        email: Joi.string().lowercase().email(),
        password: Joi.string().max(100),
        _csrf: Joi.string().token()
      },
      type: 'form'
    },
    handler: async (ctx) => {
      console.log('sth'); // does not print 
      ctx.status = 200;
      ctx.body = ctx.request.body;
    }
  }
]);

const app = new Koa();

// add this for test.
app.use(async (ctx, next) => {
  ctx.request.body = await parse.form(ctx);
  console.log(ctx.request.body);
  // curl -d "name=test" http://localhost:3000/signup
  // ^@curl: (52) Empty reply from server
  // print: { name: 'test' }
  await next(); // always pendding
});

app.use(routes.middleware());
app.listen(3000);

i need a pre-request-handler to check whether the token is valid.

so i have 3 questions:

  1. how to fix this issue
  2. how can i parse the body before jump into the router
  3. how to support multiple validate.type like ['json','form']

fn is not run before specific route when using router.use(path, fn)

Document says:

To run middleware before a specific route, also pass the optional path:

const router = require('koa-joi-router');
const users = router();

users.get('/:id', handler);
users.use('/:id', runThisBeforeHandler);```

but runThisBeforeHandler is actually run after handler

Cannot find module 'await-busboy'

In the last commit, some changes have been made but it brokes the router. The dependency await-busboy doesn't exist. I used Node v7.0.0 with --harmony flag and I ran npm install twice.

Thanks for the amazing work on this repository!

node --harmony server.js
module.js:474
    throw err;
    ^

Error: Cannot find module 'await-busboy'
    at Function.Module._resolveFilename (module.js:472:15)
    at Function.Module._load (module.js:420:25)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/aurelien/repositories/strapi/packages/strapi/node_modules/koa-joi-router/joi-router.js:9:16)
    at Module._compile (module.js:573:32)
    at Object.Module._extensions..js (module.js:582:10)
    at Module.load (module.js:490:32)
    at tryModuleLoad (module.js:449:12)
    at Function.Module._load (module.js:441:3)
    at require (internal/module.js:20:19)

Prefix seems to be broken

ubuntu 16.04
Node.js 10.15.1
koa 2.7.0
koa-joi-router 5.2.0

'use strict'

const Koa = require('koa')
const app = new Koa()

const router = require('koa-joi-router')

const orders = router()
orders.get('/', ctx => {
  ctx.body = 'Orders response'
})

orders.prefix('orders')

app.use(orders.middleware())

app.listen(3000)

Actual behavior:

localhost:3000/orders returns 404 Not Found

Expected behavior:

localhost:3000/orders returns 200 'Orders response`

Connected issue created in koa-router

Expose the Joi validator as a middleware

Right now the only way to use the Joi middleware is to pass a configuration to the router. It would be more flexible to be able to use the middleware directly.

For example, instead of:

router.get('/user', config, handler)

It should be possible to do:

router.get('/user', joiMiddleware(config), handler)

This is more inline with a middleware based framework, allows the validation to be used after other middlewares, and enables wrapping and testing the validator.

I think this could be done in 2 ways. Either expose the middleware directly from the module, or extract the middleware to a joi-middleware project on which joi-router would depend. The second option is my personal favorite since it would also allow the middleware to be used with other routers. The first option however is definitely easier to implement.

this.state.route cannot get custom error in validate

function makeSpecExposer(spec) {
  var defn = clone.clonePrototype(spec);
  return function* specExposer(next) {
    this.state.route = defn;
    yield* next;
  };
}

Why use clone to expose specs? It will produce some trouble like 'custom error will transform to undefined'.

Is there a way to set global joi config?

I want to set joi allowUnknown as a global config, because I don't mind what additional fields are passed in as long as the fields I want are supplied.
I have to set allowUnknown in every Joi valid code, is there a way to set global Joi config like this below?

const router = require('koa-joi-router');
const pub = router({
  validate: {
    allowUnknown: true,
  },
});

Is this project still maintained?

Noticed that it's almost 10 months without any update. The major version of Joi is already 14 but that of the bundled one is just 10.

Anyway, great package with nice design!

date().iso() is wrongly claiming input is invalid

Using

validate: {
  type: "form",
  body: {
    start: Joi.date().iso().greater("now"),
  }
}

The form input of start="2018-10-10T10:00" or start="2018-10-10T10:00Z" both lead to

child "start" fails because ["start" must be a valid ISO 8601 date]

However Joi.validate("2018-10-10T10:00", Joi.date().iso().greater("now")) => error === null

How do I multipart?

Hello everyone,
I saw the docs and I have managed to successfully receive every type of requests except for multipart..

I copied the given code:
``admin.route({
method: 'post'
, path: '/blog'
, validate: { type: 'multipart' }
, handler: function *(){
var parts = yield this.request.parts
var part

while (part = yield parts) {
  // do something with the incoming part stream
  part.pipe(someOtherStream)
}

console.log(parts.field.name) // form data

}
})``

But when I run my server I immediatly get the following error:
TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: "undefined"

If I use co-busboy myself meaning:
var parts = parse(this) var part while (part = yield parts) { console.log (part); if (part.length) { // arrays are busboy fields console.log('key: ' + part[0]) console.log('value: ' + part[1]) } else { // otherwise, it's a stream part.pipe(fs.createWriteStream('some file.txt')) } }
It works amazingly perfect

I know it has something to do with generators but don't get why..
Also - am I supposed to validate the fields myself? because I can't do:
validate: { type: 'multipart', body: { username: Joi.string().required(), email: Joi.string().optional() } }

Din.

can I specify base url to router?

this router is cool, but i want to add base url to the router so i can use it like this:

var pub = routuer({baseUrl:'/test'});
pub.post('/wow', ...);

then url '/test/wow' can be visited

can this be possible by some ways?

3q

How to use this middleware with koa-bodyparser

Hello and thank you all for the work done on this project.

I'm trying to integrate this middleware into a project that already uses koa-bodyparser (required middleware to koa-passport).

Unfortunately when trying to reach a route when this middleware is declared the road never answers. I guess there is a conflict with co-body somewhere.

Do we have a workaround for using this middleware with koa-bodyparser or am i going wrong ?

const koa = require('koa');
const bodyParser = require('koa-bodyparser');
const router = require('koa-joi-router');
const Joi = router.Joi;

const app = koa();
const helloRouter = router();

app.use(bodyParser()); // <-- comment and the call will work

helloRouter.route({
  method: 'post',
  path: '/hello',
  validate: {
    body: {
      name: Joi.string().required()
    },
    type: 'json'
  },
  handler: function* hello() {
    const { name } = this.request.body;
    this.body = `Hello ${name}`;
  }
});

app.use(helloRouter.middleware());

app.listen(3000);

The call : curl -X POST -H "Content-Type: application/json" -d '{ "name": "John" }' "http://localhost:3000/hello"

Dates in query string become empty strings after Joi validation

If a query parameter is validated as a Joi date object, the request will pass validation but the param will be an empty string once inside the route.

I believe this is due to the validateInput method relying on setting request['query'] to the validated Joi object. Unfortunately, this.request.query has getter/setter logic inside of Koa:

  get query() {
    var str = this.querystring;
    var c = this._querycache = this._querycache || {};
    return c[str] || (c[str] = qs.parse(str));
  },
  set query(obj) {
    this.querystring = qs.stringify(obj);
  },

The properly validated objects are being stringified by koa, and then parsed again when accessing this.request.query inside the router. It looks like boolean params also turn into strings rather than primitives which is likely the same issue.

Link to koa-docs

Hello,

Great job on this project. I really like the declarative route specs you have created and some really good forethought went into exposing the routes for analysis and having a meta property.

I am building a number of micro-services and want some documentation to go along with them so I have created a small project that renders documentation from the route specs provided by koa-joi-router. The project is koa-docs. I would appreciate a link from here so more people can use/contribute to it.

Thanks

Feature: Allow multiple types

It seems it would make sense to allow clients to request resources with either json, url encoded or form data. All three could be allowed by the API.
Currently the type argument in the validation is only accepting a string.
I could create a PR that would accept an array too.

Nested router prefix

How I can have sub router with prefix and add main router with another prefix?

userRoutes.js

import router from 'koa-joi-router';
const userRouter = router();
userRouter.prefix('/user');
const userRoutes = [...];
userRouter.route(userRoutes);
export default userRouter;

mainRoutes.js

import router from 'koa-joi-router';
import userRouter from 'userRoutes';
apiRouter.prefix('/app');
apiRouter.route(userRouter.routes);
export default apiRouter;

server.js

import AppApiRouter from 'mainRoutes';
app.use(AppApiRouter.middleware());

So actually, now my userRouter uri should be '/app/user/path'. Instead it is replacing prefix with new prefix string. In mainRoutes.js , apiRouter.route is directly accessing userRoutes.routes object. We are adding prefix as key in router object, that's why mainRouter is not able to get new path.

How to solve this?

Build issue using v 4.0.0 due to dependency on your koa-router fork?

We are still using version 4.0.0 of this package which has a specific commit dependency on your fork of koa-router:

git+https://github.com/aheckmann/koa-router.git#f0c60d374b5903f

This has started breaking in our recent builds. Is it possible you changed something on your fork that would break this?

When we try and npm install we get a git error:

npm info git [ 'rev-list', '-n1', 'f0c60d374b5903f' ]
npm ERR! git rev-list -n1 f0c60d374b5903f: fatal: ambiguous argument 'f0c60d374b5903f': unknown     revision or path not in the working tree.

We can see that commit in github, but it does not seem to appear in any branch?

Support KoaRouter#param middlewares

Documentation: KoaRouter#param

That is, validate param input before KoaRouter calls the param middleware so that we can get the validated value in the middleware function.

router.route({
  path: '/thing/:id',
  handler: function* () {
    // this.thing is set using router param middleware, below
    this.body = this.thing
  },
  validate: {
    params: {
      id: Joi.number()
    }
  }
})

router.router.param('id', function* (id, next) {
  // imagine db.getThing will throw if id is not a number
  // currently this will fail if id is not a number since it is not yet validated
  this.thing = yield db.getThing(id)
  yield next
})

Use recent Joi

The latest Joi version is 9 but this module uses a 1 year old version 6. Any plans to update it?

request hangs if joi-router used together with a body parser

NodeJS: 8.6.0
npm: 5.2.0
OS: Max OS X 10.12.2
koa: 2.4.1
koa-joi-router: 5.0.0
koa-bodyparser: 4.2.0
koa-body: 2.5.0

For the following app:

const koa = require('koa');
const router = require('koa-joi-router');
const bodyParser = require('koa-bodyparser');

const public = router();

public.route({
  method: 'post',
  path: '/foo',
  validate: {
    type: 'json',
  },
  handler: async (ctx) => {
    ctx.status = 201;
  }
});

const app = new koa();
app.use(bodyParser());
app.use(public.middleware());
app.listen(2999);

the request hangs. The same if koa-body used instead if koa-bodyparser.

I need a koa body parser to be able to use request body in the middlewares.

Run middleware for a specific path + method

Hi! Thanks for your amazing work on this router!

I was wondering, if the router is able to execute a middleware before hitting a specifc path + method. In the documentation, I saw that is possible for a specific path but without method allocated to it.

var router = require('koa-joi-router');
var users = router();

users.get('/user/:id', handler);
users.use('/:id', runThisBeforeHandlerForGET);

users.post('/user', handler);
users.use('/:id', runThisBeforeHandlerForPOST);

Is it possible?

install error with security connect problem ( in China )

I can't install this package, with error: ( in China , it's OK in AWS us)

$ npm -v
5.4.2
$ npm install koa-joi-router
npm WARN deprecated [email protected]: XSS vulnerability fixed in v1.0.3
npm ERR! Error while executing:
npm ERR! /usr/bin/git ls-remote -h -t https://github.com/aheckmann/koa-router.git
npm ERR! 
npm ERR! fatal: unable to access 'https://github.com/aheckmann/koa-router.git/': Unable to communicate securely with peer: requested domain name does not match the server's certificate.
npm ERR! 
npm ERR! exited with error code: 128

By the way, I found the dependency version is very old compare to the original, can you upgrade it ?
And I wonder why not depend on the original koa-router ?

  1. dependency in package.json is : "koa-router": "git+https://github.com/aheckmann/koa-router.git#19e60d9"
  2. https://github.com/aheckmann/koa-router is now in version 5.1.2.
  3. https://github.com/alexmingoia/koa-router ( the original ) is now 7.3.0.

Query validator doens't work

Hello, genius, I found that it seems the query validator doesn't work. Here is my code:

const Koa = require('koa')
const app = new Koa()

const router = require('koa-joi-router')
const Joi = router.Joi
const p = router()

p.route({
  path: '/',
  method: 'get',
  validate: {
    query: {
      t: Joi.string().min(3)
    },
    body: {
      email: Joi.string().email()
    },
    type: 'json'
  },
  handler: async ctx => {
    ctx.body = 'hello'
  }

})
p.prefix('/api/v1/public')
app.use(p.middleware())

image

If the email is missed in the request body, it works very well. However, it doesn't throw any error without a t in the request query even with the email in the body. What's wrong?

image

ability to specify Joi validation options without using Joi objects as schemas

Hey there!

Considering using koa-joi-router on our next few projects.

One issue I ran into is no ability to set options for Joi other than specifying the validation as Joi objects everytime and chaining an options call.
body: joi.object({ email: joi.string().email().required(), password: joi.string().required(), }).options({abortEarly: false}),

This is a bit tedious if you want something set for every validation everywhere.
F.ex. I'd like abortEarly: false to get all validation errors at once.

The way I see it, it could be done 2 ways:

  1. have individual options in the validation spec (abortEarly, convert, etc.)
  2. whole options object in the validation spec

Potential problems:
These "global" options overriding individual validations sent as Joi objects with options specified (like in the example above).

I'd be willing to tackle this and submit a PR if everyone's ok with the changes - already have solution 1. ready.

default query string options do not work for empty query string

OS: Max OS X 10.12.2
Node version: 8.6.0
npm version: 5.2.0
koa version: 2.4.1
koa-joi-router version: 5.0.0
joi version: 9.2.0

I have the following route:

module.exports = {
  method: 'get',
  path: '/',
  validate: {
    query: Joi.object().keys({
      search: Joi.string(),
      sort: Joi.object().default({ 'name': 1 }),
    }),
  },
  handler: async (ctx) => {
    const { sort } = ctx.query;
    console.log(sort);
    ctx.status = 200;
  },
};

The actual behavior:
when I specify a query string ex. ?search=foo, I get the correct default value for sort parameter, however for no query string I get undefined

The expected behavior:
even for no query string, the sort parameter should have { 'name': 1 } as default value

Object/named array in query string

I wanted to validate a object in query string (?deactivated[from]=2019-05-23T08:18:45.283Z&deactivated[to]=2019-06-12T08:18:45.283Z)
So I went ahead and tried:

query: {
  deactivated: {
    from: Joi.date(),
    to: Joi.date(),
  },
}

But the request just prints out ("deactivated[from]" is not allowed).

I have discovered that ?deactivated={"from": "2019-01-01T08:18:45.283Z"} works, but I would like the use the first approach, as that's how objects are converted to query string by default.
Is there any way I can get this behaviour?

From further testings, if I define the query like so:

query: {
  "deactivated[from]": Joi.date(),
  "deactivated[to]": Joi.date(),
}

It seems to work, but that's not a solution, the variable names are unusable.

API versioning based on Accept header

How would one go about building an API where the version is not part of an URL?

Other popular methods include using HTTP Accept header, custom request headers, etc.

It seems like a good way to achieve this would be by introducing condition to .route() options which can simply be a middeware function that returns a boolean. If the true it would continue to validate and execute handlers, if not it would simply skip to the next route(if any) with the same path and method.

This would give us an ability to inspect headers and execute the right validation and handlers based on the API version.

Thoughts?

Any plan to use npm package instead of git commit for koa-router

I try to package my node app with docker. I used node alpine for base image, of course for less container size, but node alpine does not contain git to minimise image size and we are using git for aheckmann-koa-router package, so my docker built is throwing an error for alpine version

npm ERR! git clone --template=/root/.npm/_git-remotes/_templates --mirror https://github.com/aheckmann/koa-router.git /ro
ot/.npm/_git-remotes/git-https-github-com-aheckmann-koa-router-git-19e60d9-704f0691: undefined

npm ERR! Linux 4.4.0-72-generic
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" "--production"
npm ERR! node v7.9.0
npm ERR! npm  v4.2.0
npm ERR! code ENOGIT

npm ERR! not found: git
npm ERR!
npm ERR! Failed using git.
npm ERR! This is most likely not a problem with npm itself.
npm ERR! Please check if you have git installed and in your PATH.

I verified in my app, this is the only package using git for koa-router, so it will be better if we use it npm package so that we can reduce the container size from 83MB to 14MB.

node docker image link

related #39

await-busboy not working properly

Multipart request not returning proper ctx.request.parts object. returns FileStream object without path key which contains file temp path and form-data key-values are also not returning. I tried async-busboy. works fine with the same request. It returns {files, fields}, if you say, I can take a chance to migrate from await-busboy to async-busboy.

Will joi-router add language support?

As in joi document, the validate method receives 4 params.
validate(value, schema, [options], [callback])

Some options in validation is useful like language - overrides individual error messages. Defaults to no override ({}). Messages apply the following rules :

So will joi-router add options support in the future when use the validate method? ๐Ÿ˜‹

Access current route definition

I would like to use my own middleware on the routes. I know how to define it from the documentation, and the middleware is hit, but I would like to access the current route definition in it. Similar to how joi-router adds the validate property to the route definition, I would like to add my own (roles).
How can I access the current route definition from my middleware?

ctx.request.parts

like #44

handler: async (ctx) => {
      const parts = ctx.request.parts;
      const file = await parts;
      const ext = file.filename.split('.').pop();
      if (fileExt.indexOf(ext) === -1) {
        ctx.status = 400;
        return;
      }

// passed in, but cannot get

      const type = parts.field.type;
      const types = ['sth'];
      if (types.indexOf(type) === -1) {
        ctx.status = 400;
        return;
      }

      const filename = `sth`;
      const result = await createBlockBlobFromStream(type, filename, file);

      ctx.status = 200;
      ctx.body = {status: 1, data: {filename}};
    }

and i changed it to:

handler: async (ctx) => {
      const parts = ctx.request.parts;
      let part;
      let file;
      /* eslint no-cond-assign:0, no-await-in-loop:0 */
      while (part = await parts) {
        if (part.filename) {
          file = part;
        }
        part.resume();
      }
      const ext = file.filename.split('.').pop();
      if (fileExt.indexOf(ext) === -1) {
        ctx.status = 400;
        return;
      }

// can get form-data fields

      const type = parts.field.type;
      const types = ['sth'];
      if (types.indexOf(type) === -1) {
        ctx.status = 400;
        return;
      }
      const filename = `sth`;

// cannot upload to oss service, hanging without any err

      const result = await createBlockBlobFromStream(type, filename, file);

// never print
console.log('123');

      ctx.status = 200;
      ctx.body = { status: 1, data: { filename } };
    }

p.s.

exports.createBlockBlobFromStream = (container, filename, blob) => {
  const deferred = {};
  deferred.promise = new Promise((resolve, reject)=>{
    deferred.resolve = resolve;
    deferred.reject = reject;
  });
  blob.on('error', (err) => {
    deferred.reject(err);
  });
  blob.pipe(blobService.createWriteStreamToBlockBlob(container, filename));
  blob.on('end', () => {
    deferred.resolve(1);
  });
  return deferred.promise;
};

is there a nice way for await-busboy?

handler: async (ctx) => {
    const parts = ctx.request.parts;
    let part;

    try {
      while ((part = await parts)) {
        // handle files at the first place
        part.pipe(someOtherStream);
      }
    } catch (err) {
    }
    // we need to use fields data first
    console.log(parts.field.name);
  }

we need to use fields data first, such as check username/password.

if the request is valid, then upload file to third party file storage service, like Azure-Blob-Storage

here is a solution that can work:

const Koa = require('koa');
const router = require('koa-joi-router');
const { Writable } = require('stream');
const { getDefer } = require('@dwing/common');

const handleFile = (part) => {
  const deferred = getDefer();
  const stream = new Writable();
  stream.buffers = [];
  stream.length = 0;
  stream._write = function (chunk, encoding, next) {
    this.length = this.length + chunk.length;
    this.buffers.push(chunk);
    next();
  };

  stream.once('finish', function () {
    const buffer = (this.buffers.length === 0 ? new Buffer(0) : (this.buffers.length === 1 ? this.buffers[0] : Buffer.concat(this.buffers, this.length)));
    deferred.resolve(buffer);
  });
  stream.once('error', (err) => {
    deferred.reject(err);
  });

  part.pipe(stream);
  return deferred.promise;
};
const multipart = async (parts) => {
  const result = {
  };
  try {
    let part;
    // eslint-disable-next-line
    while ((part = await parts)) {
      if (part.length) {
        result[part[0]] = part[1];
      } else {
        const field = part.fieldname;
        result[field] = {
          filename: part.filename,
          mime: part.mime,
          mimeType: part.mimeType,
          encoding: part.encoding
        };
        const file = await handleFile(part);
        result[field].length = file.length;
        result[field].buffer = file;
      }
    }
  } catch (err) {
    // handle the error
  }
  return result;
};

const app = new Koa();
const routes = router();
routes.route([{
  method: 'post',
  path: '/upload',
  validate: {
    type: 'multipart',
    maxBody: '5mb'
  },
  handler: async (ctx) => {
    const parts = ctx.request.parts;
    const body = {
      ...await multipart(parts),
      ...parts.field
    };
    console.log(body);
    ctx.body = '1';
  }
}]);

app.use(routes.middleware());
app.listen(3000);

but we wonder if there is a better way, that can copy filestream for later use..

Can't PUT or POST

Hi,
First, thanks for the awesome package, i just begin to use it.
I make separation between routes, controllers and my handlers.
All my GET routes of my crud works fine, but when i want to make validation on my POST and my PUT routes, it doesn't work.
If i comment validate{}, in my route, it works. I think it's joi. But in the top of my file i have:

var router = require('koa-joi-router');
var Joi = router.Joi;

You can see a simple exemple of the skeleton of my API. The real project have a complete CRUD, if you want to see it, i can share the other pages. I separate my routes definitions in 3 parts, the route.js file :

var router = require('koa-joi-router');
// Controllers
var messageCntrl = require('./api/controllers/message');
module.exports = function(app) {
  var general = router();
  general.prefix('/api');
  general.route(messageCntrl.post);
// others routes
}

The controller:

var router = require('koa-joi-router');
var Joi = router.Joi;

// Handlers
var messageHndlr = require('../handlers/message');

var ctrl = module.exports = {};
// Need to fix
ctrl.post = {
  method: 'post',
  path: '/message',
   validate: {
     body: {
       name: Joi.string().max(100)
     },
     type: 'json',
   },
  handler: messageHndlr.post
};

The handler

var hdlr = module.exports = {};
hdlr.post = function *(next){
  yield next;
  var error, request, result;
  // console.log(this.request.body);
  console.log('dans le handler');
  try {
    var request = new Message(this.request.body);
    result = yield request.save();
    this.status = 200;
    return this.body = result;
  } catch (error) {
    // console.log(error);
    return boom.wrap(error, 400);
  }
};

You can see screen if you want but i don't have a lot of other informations:
With validate in my controller (just loading long time and show this):
with-validation

When i comment validate in my controller:
without-validation

It doesn't works for body validate not for params, and i didn't test for headers. I expose POST route but it's exactly the same for my PUT route.

Thanks for your help

Awaiting multipart "parts" hangs when a file field is followed by text fields

If I get a multipart request that has a file field first with a subsequent text field, the second await call will hang indefinitely.

If I have a text field first, the first await will have the file stream and the second await will still hang but the ctx.request.parts.field array will be populated properly with the text fields.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.