Node.js 学习笔记 - express 的使用

以前一直使用 PHP+MySQL 做服务端的 API 服务,后来用了 JavaScript 做前端开发后,发现 JS 其实很好用,而且代码库非常丰富,尤其在云函数开发中使用了mongoDB后,更是欲罢不能,所以决定后端也用 JS 来做开发,并尝试使用 mongoDB 作为数据库。后端 Node.js 开发很多都采用 express 来开发 web 服务,所以我对在 express 中遇到的问题做了个学习笔记,以备自己查阅,也备同学们参考!

1、安装 express

首先在一个合适的位置建立目录:express-demo;
在 windows 命令行界面(开始-运行-cmd),运行 shell 命令:

cd express-demo
npm init -y  ;; 初始化
npm install express --save  ;; 安装express

安装完成!

2、做一个最简单的 express 网站

2.1、创建服务主控文件

在当前目录(express-demo)下,新建app.js文件:

/**
 * 初始三部曲:引入、实例化、端口
 **/
const express = require(`express`) // 引入 express 模块
const app = express() // 创建一个 express 应用实例 app
const port = 3000 // 设置服务端口

//启动监听
app.listen(port, () => {
    console.log(`express is running at port ${port}!`)
})

这样就建立了一个追简单的 express web 服务器!

2.2、执行 app.js 启动 web 服务

node app.js

启动完成!
在浏览器里输入:http://127.0.0.1:3000,会出现错误提示:Cannot GET /
这其实是很正常的,因为我们还没有设置路由呢!

2.3、增加一个路由

修改 app.js,添加 Api 调用,使用 express 的 get 方法,访问业务模块的对应功能

app.get('/', (req, res) => res.send('index page!'))

修改后 app.js 整体源码是这样的:

/**
 * 初始三部曲:引入、实例化、端口
 **/
const express = require(`express`) // 引入 express 模块
const app = express() // 创建一个 express 应用实例 app
const port = 3000 // 设置服务端口

// 添加 Api 调用,使用 express 的 get 方法,访问业务模块的对应功能
app.get('/', (req, res) => res.send('index page!'))

//启动监听
app.listen(port, () => {
    console.log(`express is running at port ${port}!`)
})

修改 app.js 文件后,需要重启服务才能起作用!在命令行窗口 ctrl+C 组合键停止 app.js 的运行,再重新运行:

node app.js

访问 http://127.0.0.1:3000,访问成功。
到此,一个最简单的 web 服务器就搭起来了,开发起来也很简单吧?但功能也很有限不是,这只是个最基本操作步骤,下面会继续完善!

2.4、【附加题】安装 nodemon

有没有发现每次修改源码后,都要重启服务,是不是很麻烦?所以就装一个专门的代码监控工具,当代码变化时,自动重启应用。这个工具就是nodemon,既然是 node 插件,安装就必须很简单了,npm就可以:

npm install nodemon --save

安装完成后,在命令行里,使用:nodemon app.js 来代替前面使用的:node app.js 即可!试试,是不是很爽?

3、升级 -- 加入独立的路由文件

3.1、加入路由功能

上例中,app.get('/', (req, res) => res.send('index page!')),是 express 的 get 方法的使用,不能算作是路由。
而使用路由的模式,代码更简洁、模块化更好,所以下面添加路由方法,在添加路由的过程中,会有很多变形。
在 app.js 文件里增加:

// 添加 users 路由器,路由到用户管理模块
const usersRouter = express.Router() // 创建一个子路由模块 usersRouter

// 将路由器添加到应用上,/users 是路由路径,将路径和子路由模块 usersRouter 挂接起来
app.use('/users', usersRouter);

// usersRouter 的路由条目设置。注意这时候再加路由,就可以不带前面的/users路径了
usersRouter.get('/', function (req, res) {
    res.send('后台获取用户列表数据,并通过此接口返回');
});

在浏览器访问:http://127.0.0.1:3000/users,显示返回字符串:“后台获取用户列表数据,并通过此接口返回”,说明路由配置且工作正常。
分析:新增的代码有三行:
第一行:创建一个 Router
第二行:将 Router 挂接起来
第三行:增加路由映射,执行业务逻辑
到此时,app.js 的整体代码为:

/**
 * 初始三部曲:引入、实例化、端口
 **/
const express = require(`express`) // 引入 express 模块
const app = express() // 创建一个 express 应用实例 app
const port = 3000 // 设置服务端口

// 添加 Api 调用,使用 express 的 get 方法,访问业务模块的对应功能
app.get('/', (req, res) => res.send('index page!'))

// 添加 users 路由器,路由到用户管理模块
const usersRouter = express.Router() // 创建一个子路由模块 usersRouter

// 将路由器添加到应用上,/users 是路由路径,将路径和子路由模块 usersRouter 挂接起来
app.use('/users', usersRouter);

// usersRouter 的路由条目设置。注意这时候再加路由,就可以不带前面的/users路径了
usersRouter.get('/', function (req, res) {
    res.send('后台获取用户列表数据,并通过此接口返回');
});

//启动监听
app.listen(port, () => {
    console.log(`express is running at port ${port}!`)
})

3.2、加入独立的路由文件

上例中,userRouter 写在 app.js 入口文件中,这样当路由增多时,app.js 文件会变得异常臃肿,所以考虑将模块路由从 app.js 中独立出来,只在 app.js 引用即可。

3.2.1、在项目根目录下(和app.js 文件平级)建立 routers 目录,在目录下创建一个文件 users.js ,表示这是一个 users 相关的路由文件,内容为:

const express = require('express'); // 导入 express 服务对象
const router = express.Router(); // 获取 express 的路由对象

// 注意:下面的路由路径为子路径,此例中实际路径需要加上前缀/users

router.get('/', (req, res) => {
    res.send('users list page!')
})

router.get('/:id', (req, res) => {
    res.send(`id 为:${req.params.id} 的用户信息!`)
})

module.exports = router

3.2.2、修改 app.js 文件中的 路由定义和 use 语句,将

const userRouter = express.Router()
app.use('/users', userRouter)
userRouter.get('/', function(req, res){
    res.send('后台获取用户列表数据,并通过此接口返回')
})

修改为:

app.use('/users', require('./routers/users'))

测试运行,没有问题,这样就成功把路由文件剥离出来了。

3.3、加入 routers.js

按上面的做法,增加另外一个 router ,比如订单路由: orders 。
先增加路由文件 orders.js ,内容如下:

const express = require('express'); // 导入 express 服务对象
const router = express.Router(); // 获取 express 的路由对象

// 注意:下面的路由路径为子路径,此例中实际路径需要加上前缀/orders

router.get('/', (req, res) => {
    res.send('orders list page!')
})

router.get('/:id', (req, res) => {
    res.send(`id 为:${req.params.id} 的订单信息!`)
})

module.exports = router

同样,还是需要修改 app.js 文件,需要加上一行:

app.use('/orders', require('./routers/orders'))

虽然不是很复杂,但每次增加路由模块,都会修改 app.js ,感觉还是不爽,还是需要升级,继续变形看看...,具体做法就是

3.3.1、增加一个路由汇总定义文件: ./routers.js

var routers = function(app){
    app.use('/users', require('./routers/users'))
    app.use('/orders', require('./routers/orders'))
}

module.exports = routers

3.3.2、修改 app.js 文件

将 app.js 里的两个对 /users 和 /orders 两个路由的 use 语句删除掉,增加一句,将路由汇总文件 routers.js 导入

require('./routers.js')(app) // app作为参数传递给 routers() 函数

至此,对于路由的修改,基本就不用动 app.js 文件了,需要增加新的路由时,添加新的路由文件,并修改 ./router.js 的相应内容即可。

备注:此例子中的 app.get('/',.....) 也可以改成路由器模式,再建立一个 indexRouter 就可以了,如果 index 的功能很简单的话,index 部分不建立路由器,直接使用 express 的 get、post等方法也没问题。

4、升级 -- 路由文件和业务分离

4、升级 -- 路由文件和业务分离
由前所述,路由文件由 ./routers.js 汇总定义文件和 routers 目录下的具体路由模块文件组成,路由结构清晰多了,但目前路由模块文件里还是涉及到很多的业务逻辑,能否将业务和路由剥离呢?经测试,答案是肯定的。具体做法如下:

4.1、在项目根目录下建立文件夹 controllers

在此目录下,为每个路由模块建立一个控制器模块,例如此例中的 users.js 和 orders.js:

users.js

var controller = {}

// list 方法对应 get('/users/',....) 路由
controller.list=function(req, res){
    res.send('users list page!')
}

// info 方法对应 get('/users/:id',...) 路由
controller.info=function(req, res){
    res.send(`id 为:${req.params.id} 的用户信息!`)
}

module.exports = controller

orders.js

var controller = {}

// list 方法对应 get('/orders/',....) 路由
controller.list=function(req, res){
    res.send('orders list page!')
}

// info 方法对应 get('/orders/:id',...) 路由
controller.info=function(req, res){
    res.send(`id 为:${req.params.id} 的订单信息!`)
}

module.exports = controller

4.2、修改子路由文件

修改路由模块文件里的 router.get()方法,如下:

./routers/users.js

var controller = require('../controllers/users')
router.get('/', controller.list)
router.get('/:id', controller.info)

./routers/orders.js

var controller = require('../controllers/orders')
router.get('/', controller.list)
router.get('/:id', controller.info)

从上面的修改可以看出,路由模块的引用很单纯,除了模块名称不同、应用文件不同,其他都差不多。而和逻辑有关的代码都放到controllers下面去了,如果后面再加上数据模块 models ,是不是和以前 PHP 的 MVC 很类似?这样,express 和路由相关的架构就基本确定,后续就可以将重点放到逻辑代码的开发上了。

后续如果有时间,会继续研究一下数据模块 models 的构建模式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335