Router

Router is mainly used to describe the corresponding relationship between the request URL and the Controller that processes the request eventually. All routing rules are unified in the app/router.js file by the framework.

By unifying routing rules, we can avoid the routing logics scattered in many places which may cause many unknown conflicts, and we can more easily check global routing rules.

# How to Define Router

// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/user/:id', controller.user.info);
};
// app/controller/user.js
class UserController extends Controller {
async info() {
const { ctx } = this;
ctx.body = {
name: `hello ${ctx.params.id}`,
};
}
}

This simplest Router is done by now, when users do the request GET /user/123, the info function in user.js will be invoked.

# Router config in detail

Below is the complete definition of router, parameters can be determined depending on different scenes.

router.verb('path-match', app.controller.action);
router.verb('router-name', 'path-match', app.controller.action);
router.verb('path-match', middleware1, ..., middlewareN, app.controller.action);
router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);

The complete definition of router includes 5 major parts:

# Notices

Here are some examples of writing routing rules:

// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/home', controller.home);
router.get('/user/:id', controller.user.page);
router.post('/admin', isAdmin, controller.admin);
router.post('/user', isLoginUser, hasAdminPermission, controller.user.create);
router.post('/api/v1/comments', controller.v1.comments.create); // app/controller/v1/comments.js
};

# RESTful style URL definition

We provide app.resources('routerName', 'pathMatch', 'controller') to generate CRUD structures on a path for convenience if you prefer the RESTful style URL definition.

// app/router.js
module.exports = app => {
const { router, controller } = app;
router.resources('posts', '/posts', controller.posts);
router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js
};

The codes above produce a bunch of CRUD path structures for Controller app/controller/posts.js, and the only thing you should do next is to implement related functions in posts.js.

Method Path Route Name Controller.Action
GET /posts posts app.controllers.posts.index
GET /posts/new new_post app.controllers.posts.new
GET /posts/:id post app.controllers.posts.show
GET /posts/:id/edit edit_post app.controllers.posts.edit
POST /posts posts app.controllers.posts.create
PATCH /posts/:id post app.controllers.posts.update
DELETE /posts/:id post app.controllers.posts.destroy
// app/controller/posts.js
exports.index = async () => {};

exports.new = async () => {};

exports.create = async () => {};

exports.show = async () => {};

exports.edit = async () => {};

exports.update = async () => {};

exports.destroy = async () => {};

Methods that are not needed may not be implemented in posts.js and the related URL paths will not be registered to Router neither.

# Router in Action

More practical examples will be shown below to demonstrate how to use the router.

# Acquiring Parameters

# via Query String

// app/router.js
module.exports = app => {
app.router.get('/search', app.controller.search.index);
};

// app/controller/search.js
exports.index = async ctx => {
ctx.body = `search: ${ctx.query.name}`;
};

// curl http://127.0.0.1:7001/search?name=egg

# via Named Parameters

// app/router.js
module.exports = app => {
app.router.get('/user/:id/:name', app.controller.user.info);
};

// app/controller/user.js
exports.info = async ctx => {
ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
};

// curl http://127.0.0.1:7001/user/123/xiaoming

# acquiring complex parameters

Regular expressions, as well, can be used in routing rules to acquire parameters more flexibly:

// app/router.js
module.exports = app => {
app.router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
};

// app/controller/package.js
exports.detail = async ctx => {
// If the request URL is matched by the regular expression, parameters can be acquired from ctx.params according to the capture group orders.
// For the user request below, for example, the value of `ctx.params[0]` is `egg/1.0.0`
ctx.body = `package:${ctx.params[0]}`;
};

// curl http://127.0.0.1:7001/package/egg/1.0.0

# Acquiring Form Contents

// app/router.js
module.exports = app => {
app.router.post('/form', app.controller.form.post);
};

// app/controller/form.js
exports.post = async ctx => {
ctx.body = `body: ${JSON.stringify(ctx.request.body)}`;
};

// simulate a post request.
// curl -X POST http://127.0.0.1:7001/form --data '{"name":"controller"}' --header 'Content-Type:application/json'

P.S.:

If you perform a POST request directly, an error will occur: 'secret is missing'. This error message comes from koa-csrf/index.js#L69.

Reason: the framework verifies the CSFR value specially for form POST requests, so please submit the CSRF key as well when you submit a form. Refer to Keep Away from CSRF Threat for more detail.

Note: the verification is performed because the framework builds in a security plugin egg-security that provides some default security practices and this plugin is enabled by default. In case you want to disable some security protections, just set the enable attribute to false.

"Unless you clearly confirm the consequence, it's not recommended to disable functions provided by the security plugin"

Here we do the config temporarily in config/config.default.js for an example

exports.security = {
csrf: false
};

# Form Verification

// app/router.js
module.exports = app => {
app.router.post('/user', app.controller.user);
};

// app/controller/user.js
const createRule = {
username: {
type: 'email',
},
password: {
type: 'password',
compare: 're-password',
},
};

exports.create = async ctx => {
// throws exceptions if the verification fails
ctx.validate(createRule);
ctx.body = ctx.request.body;
};

// curl -X POST http://127.0.0.1:7001/user --data 'username=abc@abc.com&password=111111&re-password=111111'

# Redirection

# Internal Redirection

// app/router.js
module.exports = app => {
app.router.get('index', '/home/index', app.controller.home.index);
app.redirect('/', '/home/index', 302);
};

// app/controller/home.js
exports.index = async ctx => {
ctx.body = 'hello controller';
};

// curl -L http://localhost:7001

# External Redirection

// app/router.js
module.exports = app => {
app.router.get('/search', app.controller.search.index);
};

// app/controller/search.js
exports.index = async ctx => {
const type = ctx.query.type;
const q = ctx.query.q || 'nodejs';

if (type === 'bing') {
ctx.redirect(`http://cn.bing.com/search?q=${q}`);
} else {
ctx.redirect(`https://www.google.co.kr/search?q=${q}`);
}
};

// curl http://localhost:7001/search?type=bing&q=node.js
// curl http://localhost:7001/search?q=node.js

# Using Middleware

A middleware can be used to change the request parameter to uppercase. Here we just briefly explain how to use the middleware, and refer to Middleware for detail.

// app/controller/search.js
exports.index = async ctx => {
ctx.body = `search: ${ctx.query.name}`;
};

// app/middleware/uppercase.js
module.exports = () => {
return async function uppercase(ctx, next) {
ctx.query.name = ctx.query.name && ctx.query.name.toUpperCase();
await next();
};
};

// app/router.js
module.exports = app => {
app.router.get('s', '/search', app.middlewares.uppercase(), app.controller.search)
};

// curl http://localhost:7001/search?name=egg

# Too Many Routing Maps?

As described above, we do not recommend that you scatter routing logics all around, or it will bring trouble in trouble shooting.

If there is a need for some reasons, you can split routing rules like below:

// app/router.js
module.exports = app => {
require('./router/news')(app);
require('./router/admin')(app);
};

// app/router/news.js
module.exports = app => {
app.router.get('/news/list', app.controller.news.list);
app.router.get('/news/detail', app.controller.news.detail);
};

// app/router/admin.js
module.exports = app => {
app.router.get('/admin/user', app.controller.admin.user);
app.router.get('/admin/log', app.controller.admin.log);
};

or using egg-router-plus.