一、一些小问题
1.文件操作路径和模块读取路径的问题
我们使用fs核心模块系统进行文件操作时一般这样书写路径
fs.readFile('./views/index.html');//读取views目录下的idnex.html文件
我们使用require()进行自定义模块加载时的路径一般这样写
require('js/main.js');//加载js目录下的main.js
注意区分两者的区别,./
的含义是相对路径,代表当前目录。文件操作路径不可以省略,而模块读取路径可以省略。另外如果忽略.
写成'/',那么/
代表的是磁盘根目录。
2.让服务器自动重启的第三方模块nodemon
每次都要更新js后都需要手动重新启动服务器,比较麻烦,我们可以使用nodemon
第三方模块来实现服务器自启动。
安装nodemon
npm insatll --save nodemon -g
使用
在命令行中使用nodemon代替node命令即可:
当执行的js被执行保存时服务器就会重新启动。
二、在express中使用art-template
art-template专门为express配套了第三方模块:express-art-template
,可参考官方文档进行使用:express-art-template
安装
npm insatll --save art-template npm insatll --save express-art-template
注册模板引擎
//引入模块var express = require('express');var template = require('express-art-template');var app = express();//在express中注册模板引擎app.engine('.html',template);
使用app.engine(ext,callback)
方法来注册模板引擎。callback是使用的模板引擎,ext是文件后缀。即当向页面呈现后缀为.ext的文件时,将使用指定的模板引擎进行渲染之后再呈现视图。
使用
app.get('/',(req,res) => { res.render('./index.html',{comments:comments}); });
res.render(view [, locals] [, callback])
方法就是上面说的呈现的视图的方法,即将某文件发送到页面上,一般就是html文件。
view是字符串,表示文件的路径;locals是一个对象,其属性定义了视图中的变量,模板引擎就会在内部根据传入的参数进行渲染;callback(err,html)是一个回调函数,err为错误对象,html为将要呈现是字符串。
注意
上面的视图资源路径为./index.html
,但其实找的是views/index.html。这是express默认的规则,如果没有指定视图资源路径,默认查找views目录下的文件。如果需要自定义视图资源路径,请使用:
//设置views路径,可以传入单个目录或数组目录,查找时按照数组顺序查找app.set('views',dir | dirArr)
三、案例:使用express实现留言板
1.express替代http
在之前的文章Node(2)中有一个留言板的案例,之前是使用http核心模块来实现的,现在我们使用express来实现。(html代码在之前的文章中,可自取)
var express = require('express');//服务器var app = express(); app.listen(8080,function(){ console.log('server running at 8080'); });var comments = [];//注册art-template模板引擎app.engine('html', require('express-art-template'));//配置静态资源app.use('/public',express.static('./public'));//处理get请求app.get('/',(req,res) => { res.render('./index.html',{comments:comments},function(err,html){ res.send(html); }); }); app.get('/post',(req,res) => { res.render('./post.html'); }); app.get('/addComment',(req,res) => { //获取get提交的参数==>相当于使用url.parse(req.url,true).query; var query = req.query; query.dateTime = new Date().toLocaleString(); comments.unshift(query); //重定向==>相当于res.statusCode=302;res.setHeader('Location','/') res.redirect(302,'/'); });
显而易见,之前的js代码有60余行,现在的js代码只有30余行。值得注意的几个点:
我们在使用http核心模块时,还需要手动引入fs模块,art-template模块。现在使用了express之后不需要再手动引用,因为express内部会自动引用。
之前的页面渲染逻辑是:fs读取html文件 --> 使用art-template的render()对读取的字符串进行渲染 --> 发送到页面。
现在的页面渲染逻辑是:使用
app.engine()
注册模板引擎 --> 使用res.render()
渲染页面并显示。其实express内部操作和我们之前的没有太大差别,但框架的这种封装特性极大地提高了我们的开发效率,然我们能更专注于业务。
还有简化操作的api比如:
req.query()
其实就是之前的url.parse(req.url,true).query;
;而res.redirect()
是简化后的重定向。
2.post提交表单的处理
对于get提交,我们使用req.request
即可获得提交的参数对象,但express没有提供post提交的参数获取的api。所以如果我们是post提交表单,就需要像之前一样使用req.on('data',backcall(postdata))
来接受数据,且接受的postdata数据还需要进行解码,也是十分麻烦。所以这里我们推荐使用另一个第三方插件body-parser
。
安装 body-parser
npm install body-parser --save
在express中使用body-parser
//引入模块var bodyParser = require('body-parser');//使用app.use(bodyParser.urlencoded({extended:false})); app.use(bodyParser.json());
然后使用request.body
就能获取到post提交的参数对象。
这里说一下app.use([path,] callback [, callback...])
这一方法,该方法的作用是在指定的路径下安装指定的中间件函数,即每当请求路在是指定路径下时就执行中间件函数。
其中:
#参数path为可选参数,默认为/
,表示执行根路径,即这个路劲下的所有请求都会执行。
#callback为中间件函数。如果需要执行多个函数,使用,
隔开即可。
所以,use函数相当于一个过滤器,一般是增加一些属性或功能方便我们使用。
上面两个中间件函数:
// 创建 application/json 解析 var jsonParser = bodyParser.json() // 创建 application/x-www-form-urlencoded 解析 var urlencodedParser = bodyParser.urlencoded({ extended: false })
我们可以理解为,body-parser
的这两个方法为response添加了body属性,值为post提交的参数对象。
四、案例:学生信息CRUD(增删改查)
1.准备相关文件
在bootstrap上随便扒的页面:
主页index.html
<!DOCTYPE html><html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="../../favicon.ico"> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="public/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h2 class="text-center">学生信息表</h2> <h2 class="sub-header"> <button class="btn btn-primary"><a href="/students/add">添加学生</a></button> </h2> <table class="table table-striped"> <thead> <tr> <th>#Id</th> <th>姓名</th> <th>性别</th> <th>年龄</th> <th>爱好</th> <th>操作</th> </tr> </thead> <tbody> {{each students}} <tr> <td>{{$value.id}}</td> <td>{{$value.name}}</td> <td>{{if $value.gender==0}}男{{else}}女{{/if}}</td> <td>{{$value.age}}</td> <td>{{$value.hobbies}}</td> <td><a href="/students/modify?id={{$value.id}}"> 修改 </a> |<a href="/students/delete?id={{$value.id}}"> 删除 </a></td> </tr> {{/each}} </tbody> </table> </body></html>
add.html
<!DOCTYPE html><html><head> <title>添加学生</title> <meta charset="utf-8"> <link href="/public/css/bootstrap.min.css" rel="stylesheet"></head><body> <h1 class="text-center">学生信息</h1> <form role='form' method="post" action="/students/add"> <div class="form-group"> <label>姓名</label> <input type="text" class="form-control" name="name" placeholder="请输入你的大名"> </div> <div class="form-group"> <label>性别</label> <div class="radio-inline"> <label> <input type="radio" name="gender" value="0" checked> 男 </label> </div> <div class="radio-inline"> <label> <input type="radio" name="gender" value="1"> 女 </label> </div> </div> <div class="form-group"> <label>年龄</label> <input type="number" name="age" min="0" max="800" value="18"> </div> <div class="form-group"> <label>爱好</label> <input type="text" class="form-control" name="hobbies" placeholder="请输入你的爱好"> </div> <button type="submit" class="btn btn-primary">Submit</button> </form></body></html>
bootstrap.min.js自行下载
2.路由设计
路由可以理解为收发网络的设备,可以抽象为一个映射关系表。页面的请求和执行的回调函数也是一一对应的,为了方便管理,我们一般会进行的页面请求整体设计,也可以称为路由设计。
功能 | 请求方式 | url | get参数 | post参数 |
---|---|---|---|---|
学生信息页 | get | /students | - | - |
增加学生页 | get | /students/add | - | - |
增加学生 | post | /students/add | - | id&name&age&gender&hobbies |
删除学生 | get | /students/delete | id | - |
修改学生页 | get | /students/modify | id | |
修改学生 | post | /students/modify | - | id&name&age&gender&hobbies |
根据路由设计,写出请求结构:
//主页app.get('/',(req,res) => { });//学生信息页app.get('/students',(req,res) => { });//增加学生信息页app.get('/students/add',(req,res) => { });//增加学生app.post('/students/add',(req,res) => { });//删除学生app.get('/students/delete',(req,res) => { });//修改学生app.post('/students/modify',(req,res) => { });
一般为了方便管理,也符合更好地设计思路,我们把这些请求单独放到一个js中,让每个js即每个模块都有各自统一的功能,而不是很杂:
app.js:程序的入口,负责服务器的创建及相关配置。
router.js:路由管理模块,存放路由设计信息。
那么现在就有一个问题,如果将上面的代码直接放到router.js中,该如何调用?使用exports | module.exports即可。3.使用json文件来充当数据库
既然要实现信息的CRUD,就需要一个数据库,但我们暂不需要使用数据库,使用一个json文件保存学生信息即可。
db.json
{ "students":[ {"id":1,"name":"Tom","gender":0,"age":18,"hobbies":"吃饭睡觉打豆豆"}, {"id":2,"name":"Lucy","gender":0,"age":20,"hobbies":"吃饭睡觉打豆豆"}, {"id":3,"name":"Michel","gender":0,"age":4,"hobbies":"吃饭睡觉打豆豆"}, {"id":4,"name":"Afile","gender":0,"age":33,"hobbies":"吃饭睡觉打豆豆"}, {"id":5,"name":"Joky","gender":0,"age":45,"hobbies":"吃饭睡觉打豆豆"} ] }
测试一下:
app.js
var express = require('express');var bodyParser = require('body-parser');var app = express(); app.listen('8080',() => { console.log('server running at 8080'); });//配置app.use('/public',express.static('./public')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false }))//路由var router = require('./router.js'); router(app);
router.js
module.exports = function(app){ var fs = require('fs'); //主页(直接跳到学生信息页好了) app.get('/',(req,res) => { res.redirect(302,'/students'); }); //学生信息页 app.get('/students',(req,res) => { //使用fs读取json文件 fs.readFile('./db.json','utf8',(err,data) => { if(err) return res.status(500).send('500'); var students = JSON.parse(data).students; res.render('./index.html',{ "students":students }); }); }); //增加学生信息页 app.get('/students/add',(req,res) => { res.render('./add.html'); }); //增加学生 app.post('/students/add',(req,res) => { }); //删除学生 app.get('/students/delete',(req,res) => { res.send('delete ok'); }); //修改学生 app.post('/students/modify',(req,res) => { res.send('modify ok'); }); }
效果:
4.封装操作db.json文件的方法
在CRUD中我们会多次使用fs来读取文件,为了方便调用,我们有必要对重复性的工作进行封装。
student.js
var fs = require('fs');/* 将students放到db.json中 */function write(students){ var db = { "students":students }; var dbStr = JSON.stringify(db); fs.writeFile('./db.json',dbStr,'utf8',(err) => { if(err){ return false; } }); }/*获取全部学生信息 return students[] */function queryAll(callback){ fs.readFile('./db.json','utf8',(err,data) => { if(err){ callback({}); } var students = JSON.parse(data).students; callback(students); }); }/*根据id获取一个学生信息 return Obj || null */function queryById(id,callback){ queryAll((students) => { var stu = null; students.forEach((value,index) => { if(value.id === id){ stu = value; return; } }); callback(stu); }); }/*增加一个学生 */function add(stu){ queryAll((students) => { students.push(stu); write(students); }); }/*删除一个学生 return stu */function del(id,callback){ queryById(id,(stu) => { if(stu === null){ callback(stu); return; } queryAll((students) => { students = students.filter((value,index) => { return value.id !== id; }); write(students); callback(stu); }); }); }/*修改一个学生 return Boolean */function modify(stu,callback){ queryById(stu.id,(flag) => { if(flag === null){ callback(false); return; } queryAll((students) => { students = students.map((value,index) => { if(value.id === stu.id){ return stu; }else{ return value; } }); write(students); callback(true); }); }); }//将方法导出exports.queryAll = queryAll; exports.queryById = queryById; exports.add = add; exports.delete = del; exports.modify = modify;
值得注意的是,由于fs读取文件是异步操作,而我们又需要异步操作的结果,那我们就需要
使用回调函数来获取异步操作的结果
。修改router.js
module.exports = function(app){ var stuUtil = require('./student.js'); //主页(直接跳到学生信息页好了) app.get('/',(req,res) => { res.redirect(302,'/students'); }); //学生信息页 app.get('/students',(req,res) => { stuUtil.queryAll((students) => { res.render('./index.html',{ "students":students }); }); }); //增加学生信息页 app.get('/students/add',(req,res) => { res.render('./add.html'); }); //增加学生 app.post('/students/add',(req,res) => { var stu = req.body; stu.id = Math.floor(Math.random()*1000+1);//产生随机整数 stuUtil.add(stu); res.redirect(302,'/students'); }); //删除学生 app.get('/students/delete',(req,res) => { stuUtil.delete(parseInt(req.query.id),(flag) =>{ if(flag) res.redirect(302,'/students'); else res.send('500'); }); }); //修改学生信息页 app.get('/students/modify',(req,res) => { stuUtil.queryById(parseInt(req.query.id),(stu) => { res.render('./modify.html',{"stu":stu}); }); }); //修改学生 app.post('/students/modify',(req,res) => { req.body.id = parseInt(req.body.id); stuUtil.modify(req.body,(flag) => { if(flag) res.redirect(302,'/students'); else res.send('500'); }); }); }
效果
5.使用express提供的路由管理
具体参考官方文档
app.js
var express = require('express');var bodyParser = require('body-parser');//路由var router = require('./router.js');var app = express(); app.listen('8080',() => { console.log('server running at 8080'); });//配置app.use('/public',express.static('./public')); app.engine('html',require('express-art-template')); app.use(bodyParser.urlencoded({extended:false})); app.use(bodyParser.json()); app.use(router);//挂载路由module.exports = app;
router.js
var stuUtil = require('./student.js');var express = require('express');var router = express.Router();//主页(直接跳到学生信息页好了)router.get('/',(req,res) => { res.redirect(302,'/students'); });//学生信息页router.get('/students',(req,res) => { stuUtil.queryAll((students) => { res.render('./index.html',{ "students":students }); }); });//增加学生信息页router.get('/students/add',(req,res) => { res.render('./add.html'); });//增加学生router.post('/students/add',(req,res) => { var stu = req.body; stu.id = Math.floor(Math.random()*1000+1);//产生随机整数 stuUtil.add(stu); res.redirect(302,'/students'); });//删除学生router.get('/students/delete',(req,res) => { stuUtil.delete(parseInt(req.query.id),(flag) =>{ if(flag) res.redirect(302,'/students'); else res.send('500'); }); });//修改学生信息页router.get('/students/modify',(req,res) => { stuUtil.queryById(parseInt(req.query.id),(stu) => { res.render('./modify.html',{"stu":stu}); }); });//修改学生router.post('/students/modify',(req,res) => { req.body.id = parseInt(req.body.id); stuUtil.modify(req.body,(flag) => { if(flag) res.redirect(302,'/students'); else res.send('500'); }); });module.exports = router;