# ec-router [![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT)

一个简单易用的koa2路由中间件，提供规则路由功能，不再需要复杂无趣的路由文件，路由影射表等。


## feature

1. koa2路由中间件

2. 根椐请求方法及URI进行自动路由

3. 控制器钩子，支持before,after,error

4. 配置文件及控制器热重载

## change log

如果你之前使用过ec-router，请注意新版本为了保持router的功能单一性，相对旧版本，作了比较大的简化，修改如下：

1. 去掉了自动装载数据库进行自动RESTful的功能,如果你需要此功能，请参考本文档后面的使用例子自行实现。

2. 增加_any控制器，处理无法匹配的的resource

3. 在hook中增加error方法，处理路由过程抛出的错误

4. 默认的控制器方法名称由all改为_any

5. 将resource、resourceId和action注入到 ctx.ecRouter

## install

```
npm install ec-router --save
npm test

```

也可从 [git仓库](https://github.com/tim1020/ec-router) 中下载源码，放到你项目的node_modules目录

## URI格式说明

```
 http://domain[:port]/[prefix]/[apiver]/path
 ```


**[prefix]** : 表示路径前缀（可为空），可在config中设置

**[apiver]** : 表示api的版本，在config中设置 apiVer 为true时生效，
版本规则由apiVeRegex定义，默认规则为两位版本号，比如v1.0,v11,v2,11,12.22

**path** : 为具体资源路径，根椐不同的路由类型有不同


ec-router根椐请求的方法、路径或查询参数，自动分析得出controller(resource)和action，再调用相应的方法来处理。

如果对应的controller没有找到，会在controllers目录查找_any.js来代替（使用者可在此控制器内用_any方法来实现自动处理RESTful请求）

如果对应的action没有找到，则查找_any方法代替。

如果都找不到，则抛出异常，进入_hook的error方法处理（如果有的话）


## route type

### type=1, RESTful方式

本方式使用RESTful访问，根椐请求方法和请求的资源名称、ID来处理，比如：

```
GET /res/12    // => controller=res,method=get
POST /User     // => controller=user,method=post
PUT /user/11   // => controller=user,method=put
```

其中的"res"和"user"表示资源名称


### type=2,Path方式

本方式根椐路径匹配控制器和控制器方法, path的格式为： /controller/action


如:

```
/res/list      // => controller=res, method=list
/user/add      // => controller=user, method=add
/user          // => controller=user, method=any
```


该方式不区分请求方法，可在实现控制器时根椐需求自行判断


### type=3,QueryString方式

本方式使用请求字符串进行路由判断，比如：**/apiName?c=controller&a=action**,查找controller及action的方式同type=2

(其中c,a为参数名称，可在config中修改)

如：

```
/index?c=user&a=list  // => controller=uer, method=list 
/index?c=user&a=add   // => controller=user, method=add
/index?c=user&a=      // => controller=user, method=any
```

此方法同样不区分请求方法，可在实现控制器时根椐需求自行判断


## Usage


### koa app main

```
//index.js
const Koa = require('koa')
const app = new Koa()
const ecRouter = require('ec-router')

process.env.NODE_ENV = 'dev' //开启debug log

//加载其它中间件
//如果需要自动RESTful服务，需要使用bodyParser之类的请求内容解释中间件来预处理请求参数
app.use(bodyParser())

//修改ec-router的默认配置
ecRouter.loadConfig(__dirname+'/ec-config.js')
app.use(ecRouter.dispatcher())

//use other middleware

app.listen(3000)

```

### 热加载

当在配置文件中设置了hotLoad=true(缺省值)时，ec-router支持配置文件及controller的热加载(hotLoad配置的修改不支持热更新)

如果需要使用热加载，请将配置独立成模块，再使用 ```ecRouter.loadConfig('./config.js')``` 代替 ```ecRouter.setConfig(conf)```

### controller

ec-router通过dispatch将不同的请求路由到不同的控制器方法，默认地，需要将控制器文件放置在APP根目录下的controllers目录（可在配置中修改)


1. 控制器文件名、控制器方法、资源名称等大小写敏感

2. 控制器方法的函数原型是 async (ctx) =>{} 

3. type=1时,使用get,post,put,delete来命名对应的控制器方法，type非1时，可以自行定义（对应path或querystring中的action命称），可以定义_any方法来适配不存在的方法。


```

// controllers/user.js
module.exports = {
    get : async (ctx) => {
        //ctx.req.resourceId  //effective when type=1,
        //ctx.req.resource
        ctx.body = "get User"
    },
    post: async (ctx) => {
        ctx.body = "post user"
    },
    //当action无法匹配以上方法时，会自动匹配为此方法
    _any: async (ctx) => {
        //other method
    }
}

```

### api version

对于是否应该在URI中添加api的version，不同的人有不同的看法，ec-router建议的方式在你需要版本控制时，在URI中添加。

1. 配置中设置 apiVer为true
2. 需在controllers目录下创建版本目录，并将控制文件放到相应的版本目录，如 : 路径/v1/res，会路由到 controllers/v1/res.js。

> 如果你希望使用Accept的media type或其它header来控制版本，可以在_any控制器的_any方法中，判断并依靠获得的版本号及注入到ctx.ecRouter的resource,action来require相应版本的控制器


### 通用controller

> 通用controller需要使用者自行定义并放置在controller目录，该节说明通用控制器的用途及实现方法。

#### before和after钩子

如果需要在每个控制器方法执行之前或之行都执行一些逻辑，可以使用钩子，方法是：

1. 在 controllers目录下放置控制器钩子,文件名为 _hook.js
2. 在_hook.js中实现并导出before或after方法(可同时或单独前后添加钩子)，方法原型与普通controller方法一样


```
module.exports = {
    //do before all controller action
    before : async (ctx) => {
        ctx.set("Access-Control-Allow-Origin", "*") 
        ctx.set("Access-Control-Allow-Headers", "Origin, Content-Type") 
    },

    //do after all controller action
    after: async (ctx) => {
        console.log('controller finish')
    },
    //路由时抛错的处理
    error: async(ctx,err) =>{

    }
}
```

注意：只有控制器能正常执行，before和after才能被执行，如果在调用控制器之前抛错或返回了，before和after也不会执行。

#### 默认控制器

当找不到指定的controller时，ec-router会去查找controller目录下的_any.js作为默认控制器。

使用该特性，可以实现自动的RESTful服务:

```
//_any.js
module.exports = {
    get : async (ctx) => {
        //根椐ctx.ecRouter.resource ，ctx.ecRouter.resourceId及其它query参数自动从数据库获取内容输出
    },
    post: async (ctx) => {
        //实现更新数据
    },
}
```

#### 错误控制器

在碰到无法正常处理的请求时，ec-router会抛出异常，并在最后catch这些异常，传递给_hook.js控制器的error方法处理（如果没有则只输出到控制台）

### config

可以在调用ec-router.dispatcher之前使用loadConfig来修改默认配置（config文件只需定义不使用默认值的项）

```
{
    type            : 1,                //路由方式
    uriApiName      : 'index',          //使用querystring方式时，指定API文件名，即/apiName?c=xx&m=xx
    uriCParam       : 'c',              //使用querystring方式时，指定控制器的参数名
    uriAParam       : 'a',              //使用querystring方式时，指定控制器方法的参数名
    uriPrefix       : '',               //API路径前缀，如: /prefix/controller/action
    uriDefault      : '/index',         //默认uri path
    apiVer          : false,            //是否支持版本声明
    apiVeRegex      : /^v?(\d){1,2}(\.[\d]{1,2})?$/, //版本规则,
    controllerPath  : 'controllers',    //控制器文件所在目录，相对于app根目录
    allowMethod     : ['get','post','put','delete','options'] //允许的请求方法
}
```

### 数据访问

ec-router并不包括数据访问的处理，以下只是一些建议：

1. 通过koa的middleware,建立一个数据连接的中间件，建立和获取连接，并将连接句柄注入为ctx.dbconn，然后可以在controller中使用
2. 通过_hook的before建立数据连接获取，注入ctx.dbconn
3. 建立dao基类，然后在其之上实现各对象的dao,直接使用dao调用

## License

MIT is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).

## Author

Tim<tim8670@gmail.com>