-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: translate basics/router.md (#896)
- Loading branch information
Showing
1 changed file
with
332 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
title: Router | ||
--- | ||
|
||
Router is mainly used to describe the 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, routing logics are free from scatter that may cause unkown conflicts and it will be easier for us to check global routing rules. | ||
|
||
## How to Define Router | ||
|
||
- Define the routing rule in `app/router.js` file | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.get('/user/:id', 'user.info'); | ||
}; | ||
``` | ||
|
||
- Implement the Controller in `app/controller` directory | ||
|
||
```js | ||
// app/controller/user.js | ||
exports.info = function* (ctx) { | ||
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. | ||
|
||
```js | ||
app.verb('path-match', 'controller.action'); | ||
app.verb('router-name', 'path-match', 'controller.action'); | ||
app.verb('path-match', middleware1, ..., middlewareN, 'controller.action'); | ||
app.verb('router-name', 'path-match', middleware1, ..., middlewareN, 'controller.action'); | ||
``` | ||
The complete definition of router includes 5 major parts: | ||
|
||
- verb - actions that users trigger, including get, post and so on, and will be explained in detail later. | ||
* app.head - HEAD | ||
* app.options - OPTIONS | ||
* app.get - GET | ||
* app.put - PUT | ||
* app.post - POST | ||
* app.patch - PATCH | ||
* app.delete - DELETE | ||
* app.del - this is a alias method due to the reservation of delete. | ||
* app.redirect - redirects the request URL. For example, the most common case is to redirect the request accessing the root directory to the homepage. | ||
- router-name defines a alias for the route, and URL can be generated by helper method `pathFor` and `urlFor` provided by Helper. (Optional) | ||
- path-match - URL path of the route. | ||
- middleware1 - multiple Middleware can be configured in Router. (Optional) | ||
- controller.action - it's a string, take care. The framework will find the Controller sharing the name under `app/controller`, then assign the process to the configured action method. The action can be omitted if the Controller exports a method directly. | ||
|
||
### Notices | ||
|
||
- multiple Middlewares can be configured to execute serially in Router definition | ||
- Controller must be defined under `app/controller` directory | ||
- multiple Controllers can be defined within one file, and the specific one can be specified in the form of `${fileName}.${functionName}` when defining the routing rule. | ||
- Controller supports sub-directories, and the specific one can be specified in the form of `${directoryName}.${fileName}.${functionName}` when defining the routing rule. | ||
|
||
Here are some examples of writing routing rules: | ||
|
||
```js | ||
app.get('/home', 'home'); | ||
app.get('/user/:id', 'user.page'); | ||
app.post('/admin', isAdmin, 'admin'); | ||
app.post('user', '/user', isLoginUser, hasAdminPermission, 'user.create'); | ||
app.post('/api/v1/comments', 'v1.comments.create'); // app/controller/v1/comments.js | ||
``` | ||
|
||
### RESTful style URL definition | ||
|
||
We provide `app.resources('router-name', 'path-match', 'controller-name')` to generate [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) structures on a path for convenience if you prefer the RESTful style URL definition. | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.resources('posts', '/posts', 'posts'); | ||
app.resources('users', '/api/v1/users', 'v1.users'); // app/controller/v1/users.js | ||
}; | ||
``` | ||
|
||
The codes above produces 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 | ||
PUT | /posts/:id | post | app.controllers.posts.update | ||
DELETE | /posts/:id | post | app.controllers.posts.destroy | ||
|
||
```js | ||
// app/controller/posts.js | ||
exports.index = function* () {}; | ||
|
||
exports.new = function* () {}; | ||
|
||
exports.create = function* () {}; | ||
|
||
exports.show = function* () {}; | ||
|
||
exports.edit = function* () {}; | ||
|
||
exports.update = function* () {}; | ||
|
||
exports.destroy = function* () {}; | ||
``` | ||
|
||
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 | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.get('/search', 'search'); | ||
}; | ||
|
||
// app/controller/search.js | ||
module.exports = function* (ctx) { | ||
ctx.body = `search: ${this.query.name}`; | ||
}; | ||
|
||
// curl http://127.0.0.1:7001/search?name=egg | ||
``` | ||
|
||
#### via Named Parameters | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.get('/user/:id/:name', 'user.info'); | ||
}; | ||
|
||
// app/controller/user.js | ||
exports.info = function* (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 aquire parameters more flexibly: | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, 'package.detail'); | ||
}; | ||
|
||
// app/controller/package.js | ||
exports.detail = function* (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 | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.post('/form', 'form'); | ||
}; | ||
|
||
// app/controller/form.js | ||
module.exports = function* (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](https://github.com/koajs/csrf/blob/2.5.0/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. For more detail refer to [Keep Away from CSRF Threat](https://eggjs.org/zh-cn/core/security.html#安全威胁csrf的防范) | ||
> **Note**: the verification is performed because the framework builds in a security plugin [egg-security](https://github.com/eggjs/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 | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.post('/user', 'user'); | ||
}; | ||
|
||
// app/controller/user.js | ||
const createRule = { | ||
username: { | ||
type: 'email', | ||
}, | ||
password: { | ||
type: 'password', | ||
compare: 're-password', | ||
}, | ||
}; | ||
|
||
exports.create = function* (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 | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.get('index', '/home/index', 'home.index'); | ||
app.redirect('/', '/home/index', 302); | ||
}; | ||
|
||
// app/controller/home.js | ||
exports.index = function* (ctx) { | ||
ctx.body = 'hello controller'; | ||
}; | ||
|
||
// curl -L http://localhost:7001 | ||
``` | ||
|
||
#### External Redirection | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
app.get('/search', 'search'); | ||
}; | ||
|
||
// app/controller/search.js | ||
module.exports = function* () { | ||
const type = this.query.type; | ||
const q = this.query.q || 'nodejs'; | ||
|
||
if (type === 'bing') { | ||
this.redirect(`http://cn.bing.com/search?q=${q}`); | ||
} else { | ||
this.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](./middleware.md) for detail. | ||
|
||
```js | ||
// app/controller/search.js | ||
module.exports = function* (ctx) { | ||
ctx.body = `search: ${this.query.name}`; | ||
}; | ||
|
||
// app/middleware/uppercase.js | ||
module.exports = () => { | ||
return function* (next) { | ||
this.query.name = this.query.name && this.query.name.toUpperCase(); | ||
yield next; | ||
}; | ||
}; | ||
|
||
// app/router.js | ||
module.exports = app => { | ||
app.get('s', '/search', app.middlewares.uppercase(), 'search') | ||
}; | ||
|
||
// curl http://localhost:7001/search2?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 it is a must for some reasons, you can split routing rules like below: | ||
|
||
```js | ||
// app/router.js | ||
module.exports = app => { | ||
require('./router/news')(app); | ||
require('./router/admin')(app); | ||
}; | ||
|
||
// app/router/news.js | ||
module.exports = app => { | ||
app.get('/news/list', 'news.list'); | ||
app.get('/news/detail', 'news.detail'); | ||
}; | ||
|
||
// app/router/admin.js | ||
module.exports = app => { | ||
app.get('/admin/user', 'admin.user'); | ||
app.get('/admin/log', 'admin.log'); | ||
}; | ||
``` |