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 

1
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 () => {};
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
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)
}
1
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
1
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
1
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
1
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);
};
1
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'));
};
1
2
3
4
5
6
7
8
9
10
11
12
13
  • sub redirect is not support, use app.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');
1
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

上次更新: 2022/04/15, 05:41:27
×