eggjs中实现RESTfulAPI及Router
# Rest设计响应格式
在 RESTful 风格的设计中,我们会通过响应状态码来标识响应的状态,保持响应的 body 简洁,只返回接口数据。以 topics
资源为例:
# 获取主题列表
GET /api/v2/topics
响应状态码:200
响应体:
[ { "id": "57ea257b3670ca3f44c5beb6", "author_id": "541bf9b9ad60405c1f151a03", "tab": "share", "content": "content", "last_reply_at": "2017-01-11T13:32:25.089Z", "good": false, "top": true, "reply_count": 155, "visit_count": 28176, "create_at": "2016-09-27T07:53:31.872Z", }, { "id": "57ea257b3670ca3f44c5beb6", "author_id": "541bf9b9ad60405c1f151a03", "tab": "share", "content": "content", "title": "《一起学 Node.js》彻底重写完毕", "last_reply_at": "2017-01-11T10:20:56.496Z", "good": false, "top": true, "reply_count": 193, "visit_count": 47633, }, ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 获取单个主题
GET /api/v2/topics/57ea257b3670ca3f44c5beb6
响应状态码:200
响应体:
{ "id": "57ea257b3670ca3f44c5beb6", "author_id": "541bf9b9ad60405c1f151a03", "tab": "share", "content": "content", "title": "《一起学 Node.js》彻底重写完毕", "last_reply_at": "2017-01-11T10:20:56.496Z", "good": false, "top": true, "reply_count": 193, "visit_count": 47633, }
1
2
3
4
5
6
7
8
9
10
11
12
# 创建主题
POST /api/v2/topics
响应状态码:201
响应体:
{ "topic_id": "57ea257b3670ca3f44c5beb6" }
### 更新主题
- `PUT /api/v2/topics/57ea257b3670ca3f44c5beb6`
- 响应状态码:204
- 响应体:空
------
### RESTful 风格的 URL 定义
如果想通过 RESTful 的方式来定义路由, 我们提供了 `app.resources('routerName', 'pathMatch', controller)` 快速在一个路径上生成 [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 路由结构。
router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// app/router.js module.exports = app => { const { router, controller } = app; router.resources('posts', '/api/posts', controller.posts); router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js };
上面代码就在 `/posts` 路径上部署了一组 CRUD 路径结构,对应的 Controller 为 `app/controller/posts.js` 接下来, 你只需要在 `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 = async () => {};
exports.new = async () => {};
exports.create = async () => {};
exports.show = async () => {};
exports.edit = async () => {};
exports.update = async () => {};
exports.destroy = async () => {};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
如果我们不需要其中的某几个方法,可以不用在 posts.js
里面实现,这样对应 URL 路径也不会注册到 Router。
示例:
自己常用的配置:egg-router-plus (opens new window)
'use strict'
const path = require('path')
module.exports = app => {
const { router, controller, middleware, config } = app
const { baseAPI, baseWeAPI } = config.adm
const advices = controller.advices
const module = 'advices'
// router.resources('advices', '/advices', controller.advices)
const nsRouter = router.namespace(`${baseAPI}/${module}`)
const file = middleware.file(path.join(app.config.baseDir, 'public/uploads/advices'))
nsRouter.post('/', app.role.can('auth'), file, advices.create)
nsRouter.put('/:id', app.role.can('auth'), file, advices.update)
nsRouter.delete('/:id', app.role.can('auth'), advices.destroy)
nsRouter.get('/:id', advices.show)
nsRouter.get('/', advices.index)
router.get(`${baseWeAPI}/${module}`, advices.index)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 重定向
# 内部重定向
app.router.redirect('/', '/home/index', 302);
// app/router.js
module.exports = app => {
app.router.get('index', '/home/index', app.controller.home.index);
app.router.redirect('/', '/home/index', 302);
};
// app/controller/home.js
exports.index = async ctx => {
ctx.body = 'hello controller';
};
// curl -L http://localhost:7001
2
3
4
5
6
7
8
9
10
11
12
# 外部重定向
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 中间件的使用
如果我们想把用户某一类请求的参数都大写,可以通过中间件来实现。 这里我们只是简单说明下如何使用中间件,更多请查看 中间件 (opens new window)。
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 路由设置
# 太多路由映射?
如上所述,我们并不建议把路由规则逻辑散落在多个地方,会给排查问题带来困扰。
若确实有需求,可以如下拆分:
require('./router/news')(app);
// 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);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
也可直接使用 egg-router-plus (opens new window)。
namespace('/sub', app.middleware.jsonp());
// {app_root}/app/router.js
module.exports = app => {
const subRouter = app.router.namespace('/sub');
// curl localhost:7001/sub/test
subRouter.get('/test', app.controller.sub.test);
subRouter.get('sub_upload', '/upload', app.controller.sub.upload);
// const subRouter = app.router.namespace('/sub/:id');
// const subRouter = app.router.namespace('/sub', app.middleware.jsonp());
// output: /sub/upload
console.log(app.url('sub_upload'));
};
2
3
4
5
6
7
8
9
10
11
12
13
- sub
redirect
is not support, useapp.router.redirect()
or redirect to a named router.
const subRouter = app.router.namespace('/sub');
// will redirect `/sub/go` to `/anyway`, not `/sub/anyway`
subRouter.redirect('/go', '/anyway');
// 解决方式一:just use router
router.redirect('/sub/go', '/sub/anyway');
// 解决方式二:or redirect to a named router
subRouter.get('name_router', '/anyway', app.controller.sub.anyway);
// will redirect `/sub/go_name` to `/sub/anyway` which is named `name_router`
subRouter.redirect('/sub/go_name', 'name_router');
2
3
4
5
6
7
8
9
10
11
12
# 参考链接
https://eggjs.org/zh-cn/tutorials/restful.html
https://eggjs.org/zh-cn/basics/router.html