本文演示用Express搭建Api的过程。内容涉及:模块化路由,中间件,Promise,用bluebird访问MySQL,用Express实现非RESTful风格和RESTful风格api。如有错误,欢迎指正。
本文包含一个demo项目源码,地址:express-demo。运行项目需要本地安装Node.js,MySQL,一个你习惯的数据库管理工具(我用的是Sequel Pro),postman。
可用这个命令克隆到本地:
1 | git clone https://github.com/morrxy/express-demo.git |
demo代码里包含了文章里的步骤:
1 | step1-npm-init |
查看某步骤的源码:
1 | git checkout step1-npm-init |
建立项目目录
查看源码
1 | git checkout step1-npm-init |
步骤
1 | mkdir express-todolist |
npm init
后一路回车,生成一个package.json
文件,用与保存项目依赖,项目描述等信息。
安装Express
1 | npm install --save express |
启动App并返回JSON
查看源码
1 | git checkout step2-hello |
步骤
在根目录下新建文件app.js
,作为项目启动文件。并编辑文件。
1 | ; |
这里用express()
了一个app,并对其设置了根目录的get
设置了路由,最后通过app.listen()
启动app。
然后在命令行运行,启动Node进程。之后用浏览器访问http://localhost:3000/
就可以看到响应结果。
1 | node app.js |
备注:想看到整齐的json结果可以安装这个chrom插件
模块化路由
查看源码
1 | git checkout step3-module-route |
步骤
实际项目会有很多路由,一般不把路由直接放在启动文件里,而是根据需要划分成路由模块,每个模块的所有具体路由放在各自的索引文件里,最后从启动文件里加载所有路由模块的索引文件。
假设需要两个模块user
和mission
。每个模块下各自需要r1
,r2
两个路由,即需要这4个路由/user/r1
,/user/r2
,/mission/r1
,/mission/r2
。
下面用user
当例子,说明建立模块过程:
- 在根目录下新建目录
route
用于存放s所有路由模块索引文件和索引各自的目录 - 在route目录下新建文件
user.js
,作为user模块的索引文件 - 在route目录下新建目录user,用于存放所有user模块每个路由文件
- 在/route/user目录下新建需要的文件,这里是
r1.js
和r2.js
- 在app.js引入user模块
user.js:
1 | ; |
上面的代码,用Express.Router()
建立一个子路由,用于容纳所有/user
下的路由,然后往这个路由里添加了两个get路由/user/r1
和/user/r2
。
/route/user/r1.js,定义具体业务逻辑:
1 | ; |
app.js添加如下代码,用于引入user路由模块:
1 | app.use('/user', require('./route/user')); |
按上述步骤建立mission模块,另外顺手把前面对/
的请求也放进一个单独的文件里。app.js改成了这样:
1 | ; |
重启app,就可以在浏览器请求/user/r1,/mission/r1等4个新路由了:
中间件
简介
每个具体的路由都处理特定的业务逻辑,但有时候需要对所有路由,或某个路由模块下的所有路由都进行某些业务处理,比如身份验证。这时候就可以使用中间件。
中间件是函数,之所以叫中间件,是因为其执行时间处于接受请求至返回响应周期中间。并且能访问请求对象,响应对象,和下一个中间件。中间件能执行任何代码;能修改请求和响应对象;能结束请求响应周期;能调用下一个中间件。Express是一个路由和中间件web框架,Express app基本上就是一系列中间件函数调用。
假设这个app有些路由模块需要进行身份验证,其中包括/secret
,其他路由不需要。用两个中间件实现这个需求,第一个用于检测路由,第二个用于验证身份。如果路由不需要身份验证,跳过后续身份验证中间件,交出控制权,对应的路由程序继续处理。否则执行后续的身份验证中间件,验证成功则交出控制权,secret路由继续处理。否则结束请求响应周期,返回验证失败信息。
查看源码
1 | git checkout step4-middleware |
步骤
- 步骤1:按上面模块化路由的方法,新建一个secret路由模块
- 步骤2:在根目录新建一个middleware目录,用于存放所有中间件
- 步骤3:在middleware目录下新建两个文件,
route-test.js
和session-test.js
,用于检测路由和验证身份 - 步骤4:在app.js里引入中间件
步骤3的route-test.js:
1 | ; |
可以看到,这个中间件从请求对象获得请求路径,与需要验证的路径对比,如果匹配,则用next()
把控制权转交给后面的验证程序。否则通过next('route')
跳过后面的验证中间件,转交其他路由继续处理。
步骤3的session-test.js
:
1 | ; |
可以看到,这个中间件从请求对象获取用户提交的key,并与系统的key对比,如果成功,则用next()
交出控制权,转交/secret继续处理。否则中止请求响应周期,返回验证失败信息’access deny!’。
步骤4之后的app.js:
1 | ; |
app.all()
与app.get()
类似,但匹配所有得http动词。*
匹配所有路由。把这行放在其他路由模块上面,所有请求就会先被这两个中间件处理。
重启app,在浏览器测试/secret路由:
Promise
Promise是一种处理异步io的方法,可以用来解决Node的回调地狱问题,使代码编写,修改都容易很多。这里有个介绍Promises and Asynchronous Programming。bluebird是一个Promise库,提供了很多方便的工具,之前我写过一篇使用感受,bluebird带给我的惊喜,这个demo就用bluebird演示数据库访问。
数据库配置与测试
假设这个app是个todolist管理app,用mysql存数据,需要提供对todo表的读取,新增,修改,删除。这节配置数据库连接,并用/todo/list测试。
查看源码
1 | git checkout step5-db-access |
步骤
- 1.在本地安装mysql,并创建好todo表
- 2.安装bluebird和mysql,bluebird用于实现Promise语法,mysql用于连接mysql
- 3.配置数据库连接,并用/todo/list测试
步骤1:
本地安装好mysql,并用如下命令启动:
1 | mysql.server start |
启动成功会看到1
2Starting MySQL
. SUCCESS!
之后使用任何你习惯的数据库管理工具(我用的是Sequel Pro),连上本地mysql。
创建一个数据库,叫todolit
。
然后再建一个表,叫todo
。表结构如下:
1 | CREATE TABLE `todo` ( |
往里面手动插入两条数据:
步骤2:
在项目根目录执行如下命令:
1 | npm install --save bluebird mysql |
步骤3-配置数据库连接:
在根目录新建目录config
,用于存放配置文件。在config目录下新建db.js
用于连接mysql。代码如下:
1 | ; |
mysql自带的接口是回调式的,并且每个连接用完以后都有手动释放。可去官网了解一下,并与下述的db访问方式对比。
这里有一些用bluebird库连接mysql的细节,并使用了bluebird提供的Promisify功能,把mysql包自带的callback方式转成了promise方式,使数据库访问可以接入路由的Promise链。另外使用了bluebird资源自动释放,最后导出了一个连接mysql的函数,供所有db访问使用。使用时候只需要使用Promise.using()
即可以连库,并且不用管释放连接,连接会自动释放,非常方便。
细节可参考bluebird官方参考:promisifyAll, 连库与自动释放, 资源使用。
步骤3-用/todo/list测试db访问:
用前面的方式新建路由模块todo
,和/todo/list路由,用于返回所有todo。代码如下:
1 | ; |
这里使用Promise链控制程序流,使路由过程一目了然,方便开发和修改。其中访问数据的部分独立到单独的model目录下,结构与路由模块类似,以便重复使用。
接着看一下数据访问,/model/todo/list.js:
1 | ; |
这里使用了上述的bluebird提供的资源使用api:using()
,这里注意,整个函数返回了一个访问数据库的Promise(using会返回一个promise),使访问数据库的过程融入上面的路由Promise链,否则查库数据无法返回到路由里。另外,如果需要多个db访问,可在using后增加.then()
继续其他io操作,比如:
1 | module.exports = () => { |
Promise的另一个好处是,Promise链中的所有错误都能传给后面的异常处理函数,这样一个Promise链里只需要一个最后的异常处理函数就行了。这里是sendError
函数,把它放进根目录下的helper目录(用于存放所有得辅助函数),避免重复。
最后重启app,在浏览器访问http://localhost:3000/todo/list
:
增加,修改,删除
这步需要用POST向服务器提交数据,所以服务端要安装npm包body-parser
用来解析数据,在项目根目录执行:
1 | npm install --save body-parser |
查看源码
1 | git checkout step6-db-more |
步骤
按照上一步完成的/todo/list
添加todo新增,修改,删除api,分别是/todo/add
,/todo/update
,/todo/remove
,因为这些接口的method不是get,所以用postman代替浏览器测试接口,参数和测试结果分别如下图:
这样就完成了所有todo的api。
RESTful风格的todo接口
上述todo接口也可以用RESTful风格实现:
列表:GET /todos
, 显示所有todo
增加:POST /todos
, 新增todo
单个:GET /todos/:todo_id
, 显示某个todo
修改:PUT /todos/:todo_id
, 修改某个todo
删除:DELETE /todos/:todo_id
, 删除某个todo
查看源码
1 | git checkout step7-restful-todos |
步骤
与上面非restful实现相比,model里的数据访问都可以重复使用。不同之处:路由模块索引;路由参数获取。
路由模块索引todos.js
:
1 | ; |
route
是Express提供的api,参数里可以加上冒号形式的动态路由参数,比如上面的/:todo_id
,后面再跟着动词和对应的处理函数。动态路由参数的获取可以通过req.params.todo_id
的形式:
1 | const todoId = req.params.todo_id; |
最后看下RESTful风格的postman参数形式和测试结果:
全文完。