2

这个年头前端的老铁们面试的时候基本上都能看到一条:熟悉nodeJS,对于之前没有怎么了解这块技术的前端老铁们(大神忽略),本文是一个很好的传送门,带你们入门nodeJs+express4框架,并写一个简单带有登陆功能、增删改列表功能的小后台(虽为前端,这个项目没做样式美化,基础bootstrap样式引用),全面应用nodeJs+express4框架技术点。

废话不多说,先看效果gif

本文码字比较长,可能部分老铁没有耐心看完(但比看视频学习肯定效率更高),所以老铁们可以选择收藏。最后贴上这个小练习项目源码的github地址(欢迎给个star),可以自行下载,安装依赖,运行看看。

一、创建项目
1、Express 应用生成器

通过应用生成器工具 express 可以快速创建一个应用的骨架。

通过如下命令安装

$ npm install express-generator -g

注:-g是全局安装在电脑系统,如果不想全局可以把-g去掉

 express -h

express-h 选项可以列出所有可用的命令行选项:

$ express -h

  Usage: express [options] [dir]

  Options:

    -h, --help          output usage information
    -V, --version       output the version number
    -e, --ejs           add ejs engine support (defaults to jade)
        --hbs           add handlebars engine support
    -H, --hogan         add hogan.js engine support
    -c, --css <engine>  add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
        --git           add .gitignore
    -f, --force         force on non-empty directory

例如,下面的示例就是在当前工作目录下创建一个命名为 myapp 的应用。

$ express myapp

   create : myapp
   create : myapp/package.json
   create : myapp/app.js
   create : myapp/public
   create : myapp/public/javascripts
   create : myapp/public/images
   create : myapp/routes
   create : myapp/routes/index.js
   create : myapp/routes/users.js
   create : myapp/public/stylesheets
   create : myapp/public/stylesheets/style.css
   create : myapp/views
   create : myapp/views/index.jade
   create : myapp/views/layout.jade
   create : myapp/views/error.jade
   create : myapp/bin
   create : myapp/bin/www

注意:默认是安装的jade模板引擎,如果要安装ejs模板引擎的话,输入:express -e选项,即可。

然后安装所有依赖包:

$ cd myapp 
$ npm install

启动这个应用(MacOS 或 Linux 平台):

$ DEBUG=myapp npm start

Windows 平台使用如下命令:

> set DEBUG=myapp & npm start

然后在浏览器中打开 http://localhost:3000/ 网址就可以看到这个应用了。

通过 Express 应用生成器(本机测试是-e)创建的应用一般都有如下目录结构:

.
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.ejs
    ├── index.ejs

二、开发json数据库
在myapp下创建db.js添加数据的增、删、改、查、保存功能

const repos = require('./data');
const fs = require('fs');
module.exports ={ 
//持久化写入,保存 
  store(){ 
     fs.writeFileSync(__dirname+'/data.json', JSON.stringify(repos));
    //_dirname获得当前文件所在目录的完整目录名
 }, 
//获取索引
  get(index){ 
return repos[index];
 }, 
 get list(){
    return repos;
 }, 
 add(article){
  repos.push(article);
  this.store();
 },
  del(index){
   repos.splice(index,1);
   this.store(); 
}, 
 update(index,newArticle){ 
   respos.splice(index,1,newArticle);
   this.store();
 }
}

在myapp下创建data.json,里面空json文件

接下来我们在在myapp下创建test文件,创建test_db.js测试写入功能:

'use strict';
let db = require('../db');
db.add({name:'nihao'});
console.log(db.list);

运行测试:node test_db.js

会看到data.json已经插入{name:’nihao’}

三、开发文档列表功能
1、读取data.json里数据,响应在页面上
首先,启动app.js把路由入口修改一下,让他渲染index.ejs,里面的内容由db.js里的方法将data.json里的数据渲染在index.ejs

在views文件夹里的index.ejs改写

<!DOCTYPE html>
<html> 
 <head>  
  <title></title>  
  <link rel='stylesheet' href='/stylesheets/style.css' /> 
 </head> 
 <body> 
 <ul>
 <% list.forEach(function(article){ %><li><%= article.name %></li><% })%> </ul>
 </body>
</html>

填写几条测试数据在 data.json

[
{"name":"nihaonew"},
{"name":"nihao1"},
{"name":"nihao2"},
{"name":"nihao3"}
]

启动项目:node bin/www,运行结果如下:

2、开发添加文档功能
在views文件夹里的index.ejs添加,form 表单提交,触发/add功能

<!DOCTYPE html>
<html> 
 <head>  
  <title></title>  
  <link rel='stylesheet' href='/stylesheets/style.css' /> 
 </head> 
 <body> 
<div>
  <form action="/add" method="post">
   <label><input type="text" name="name" />名字</label> 
    <input type="submit" value="添加" /> 
 </form>  
</div>
 <ul>
 <% list.forEach(function(article){ %><li><%= article.name %></li><% })%> </ul>
 </body>
</html>

在app.js里添加路由的处理,post处理

//app.use('/', index);
//app.use('/users', users);
app.get('/',function(req,res){ 
res.render('index',{list:db.list})
})
app.post('/add',function(req,res){ 
db.add({name:req.body.name});
 res.redirect('/');//重定向到首页
})

运行项目,进行测试:输入:‘的的’,点击添加便可以显示内容

3、开发删除文档功能
在views文件夹里的index.ejs添加

<!DOCTYPE html>
<html> 
 <head>  
  <title></title>  
  <link rel='stylesheet' href='/stylesheets/style.css' /> 
 </head> 
 <body> 
<div>
  <form action="/add" method="post">
  <label><input type="text" name="name" />名字</label> 
 <input type="submit" value="添加" /> 
 </form>  
</div>
 <ul>
 <% list.forEach(function(article){ %><li><%= article.name %><a href="/del?index=<%=index%>">删除</a></li><% })%> </ul>
 </body>
</html>

在app.js里添加删除路由的处理,获取索引参数执行删除操作

//app.use('/', index);
//app.use('/users', users);
app.get('/',function(req,res){ 
res.render('index',{list:db.list})
})
app.post('/add',function(req,res){ 
db.add({name:req.body.name});
 res.redirect('/');//重定向到首页
});
//删除文档选项
app.get('/del',function(req,res){ 
let index = req.query.index;
 db.del(index); 
res.redirect('/');//重定向到首页})

测试:是正常删除的

5、美化界面
在index.ejs里引入bootstrap,添加点击的时候弹框

 <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
 <script src="https://cdn.bootcss.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>


5、添加修改文档功能
修改和增加相似,点击每一列的“修改”时候时候弹框,执行方法把当前列的内容输入到弹出框里,点击保存的时候触发form表单提交然后再执行修改操作

1、在index.ejs里添加bootstrap弹框代码

<!-- 更改表单-->
 <form action="/update" method="post">  
 <div class="modal fade" id="myModal2" tabindex="-1" role="dialog">  <div class="modal-dialog" role="document">   
 <div class="modal-content">   
   <div class="modal-header">    
     <h4 class="modal-title" id="myModalLabel">Modal title</h4>          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>      </div>  
    <div class="modal-body">  
 <div class="form-group">  
  <label for="exampleInputEmail1">名字</label> 
   <input type="hidden" name="index" id="updateIndex"/> 
   <input type="text" name="name" class="form-control" id="updateName" placeholder="name"> 
 </div>   
   </div>  
    <div class="modal-footer">    
    <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>   
     <input type="submit" class="btn btn-primary" value="更改"></input>    
  </div>    
</div><!-- /.modal-content --> 
 </div><!-- /.modal-dialog --> 
</div><!-- /.modal -->
 </form>
 <ul class="list-group"> 
<% list.forEach(function(article,index){ %><li class="list-group-item"><%= article.name %><a href="/del?index=<%=index%>" class="btn btn-default" >删除</a><a href="#" onclick="edit(<%=index%>)" class="btn btn-default" data-toggle="modal" data-target="#myModal2">更改</a></li><% })%>
 </ul> 
<script> 
function edit(index){ 
//alert(index)
 $.get('/get/'+index,function(result){ 
    $('#updateIndex')[0].value = index; 
  $('#updateName')[0].value = result.name;
 })
 }; 
 </script>

2、在app.js里面配置路由:

//通过索引获取当前数据
app.get('/get/:index',function(req,res){
 var index = req.params.index;
 console.log(index) 
 var name = db.get(index); 
 res.send(name);
//返回一个json对象
});

//更新文档
app.post('/update',function(req,res){
  var index = req.body.index; 
  var name = req.body.name; 
  db.update(index,{name});
  res.redirect('/');
//重定向到首页
});

3、对应的db.js执行的是

const repos = require('./data');
const fs = require('fs');
module.exports ={ //持久化写入,保存
 store(){ 
fs.writeFileSync(__dirname+'/data.json', JSON.stringify(repos));
//_dirname获得当前文件所在目录的完整目录名
 }, 
get(index){ 
  return repos[index];
 },
 get list(){ 
  return repos;
 }, 
add(article){ 
  repos.push(article); 
  this.store();
 },
 del(index){ 
  repos.splice(index,1); 
  this.store(); 
}, 
update(index,newArticle){ 
  repos.splice(index,1,newArticle); 
  this.store(); 
}
}

测试结果:

现在我们已经做好了增删改查功能了

四、登录功能
这里我们要用到会话,是用第三方插件express-session,全局安装它:

$ npm install express-session

1、首先开发登录接口,在app.js里引入

var session = require('express-session')
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
//TODO:加入会话插件
app.use(session({
 resave:false,//添加这行 
 saveUninitialized: true,//添加这行 
 secret: 'keyboard cat'
})
//登录接口
app.post('/login',function(req,res){
 let loginName = req.body.loginName;
 let passWord = req.body.passWord; 
if (loginName === 'test' &&passWord === '123456'){ 
//会话功能
 req.session.logined = true; 
res.send("success"); 
}else{ 
res.send("error"); 
}});
//登出
app.get('/logout',function(req,res){
 req.session.logined = false; 
res.redirect('/');
//重定向到首页
});

2、在index.ejs里添加判断是否登录,如果没有登录就是出现登录,然后点击登录弹框,把输入的账号和密码进行验证通过,放行,现在添加和退出

<ul class="nav nav-pills"> 
 <% if(logined){%>
  <li role="presentation">
<a href="javascript:;" data-toggle="modal" data-target="#myModal" class="btn btn-info" role="button">添加</a>
</li> 
 <li role="presentation"><a href="/logout">退出</a></li> 
 <%}else{%>  
<li role="presentation">
<a href="javascript:;" data-toggle="modal" data-target="#loginDialog">登录</a>
</li> <%}%>
  </ul>
 <!-- 登录表单-->
 <form action="/login" method="post" id="loginDialogForm"> 
  <div class="modal fade" id="loginDialog" tabindex="-1" role="dialog"> 
 <div class="modal-dialog" role="document">  
  <div class="modal-content">  
    <div class="modal-header">    
     <h4 class="modal-title" id="myModalLabel">登录框</h4>          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>      </div>  
    <div class="modal-body">   
<div class="form-group">   
 <label for="loginName">登录名:</label>  
  <input type="text" name="loinName" class="form-control" id="loginName" placeholder="loginName"> 
 </div> 
 <div class="form-group">  
  <label for="password">密码:</label>  
  <input type="text" name="passWord" class="form-control" id="passWord" placeholder="passWord"> 
 </div>  
    </div> 
     <div class="modal-footer">  
      <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>   
     <input type="submit" class="btn btn-primary" value="登录"></input> 
     </div> 
   </div><!-- /.modal-content -->  
</div><!-- /.modal-dialog -->
 </div><!-- /.modal -->
 </form>
<script> 
function edit(index){ 
//alert(index) 
$.get('/get/'+index,function(result){
 $('#updateIndex')[0].value = index; 
  $('#updateName')[0].value = result.name;
 })
 };
$('#loginDialogForm')[0].onsubmit = function(event){ 
event.preventDefault(); 
$.post('/login',{loginName:$('#loginName')[0].value,passWord:$('#passWord')[0].value},function(result){
 if(result ==='success'){ 
  window.location.reload();//刷新页面
 }else{ 
  alert('登录失败!')
 } 
});
 return false;
 }
 </script>

测试结果:

五、Express框架组件
一、路由:

路由是由一个 URI、HTTP 请求(GET、POST等)和若干个句柄组成,它的结构如下: app.METHOD(path, [callback...], callback), app 是 express 对象的一个实例, METHOD 是一个 HTTP 请求方法, path 是服务器上的路径, callback 是当路由匹配时要执行的函数

var express = require('express');
var app = express();

// respond with "hello world" when a GET request is made to the homepage
app.get('/', function(req, res) {
  res.send('hello world');
});

路由句柄:

可以为请求处理提供多个回调函数,其行为类似 中间件。唯一的区别是这些回调函数有可能调用 next('route') 方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。

路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合,如下所示.

使用一个回调函数处理路由:

app.get('/example/a', function (req, res) {
  res.send('Hello from A!');
});

使用多个回调函数处理路由(记得指定 next 对象):

app.get('/example/b', function (req, res, next) {
  console.log('response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});

响应方法

下表中响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起。

方法    描述
res.download()    提示下载文件。
res.end()    终结响应处理流程。
res.json()    发送一个 JSON 格式的响应。
res.jsonp()    发送一个支持 JSONP 的 JSON 格式的响应。
res.redirect()    重定向请求。
res.render()    渲染视图模板。
res.send()    发送各种类型的响应。
res.sendFile    以八位字节流的形式发送文件。
res.sendStatus()    设置响应状态代码,并将其以字符串形式作为响应体的一部分发送。
app.route()

可使用 app.route() 创建路由路径的链式路由句柄。由于路径在一个地方指定,这样做有助于创建模块化的路由,而且减少了代码冗余和拼写错误。

app.route('/book')
  .get(function(req, res) {
    res.send('Get a random book');
  })
  .post(function(req, res) {
    res.send('Add a book');
  })
  .put(function(req, res) {
    res.send('Update the book');
  });
express.Router

可使用 express.Router 类创建模块化、可挂载的路由句柄。Router 实例是一个完整的中间件和路由系统,因此常称其为一个 “mini-app”。

下面的实例程序创建了一个路由模块,并加载了一个中间件,定义了一些路由,并且将它们挂载至应用的路径上。

在 app 目录下创建名为 birds.js 的文件,内容如下:

var express = require('express');
var router = express.Router();

// 该路由使用的中间件
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
// 定义网站主页的路由
router.get('/', function(req, res) {
  res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
  res.send('About birds');
});

module.exports = router;

然后在应用中加载路由模块:

var birds = require('./birds');
...
app.use('/birds', birds);

应用即可处理发自 /birds 和 /birds/about 的请求,并且调用为该路由指定的 timeLog 中间件。

二、使用中间件
中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next 的变量。

中间件的功能包括:

执行任何代码。
修改请求和响应对象。
终结请求-响应循环。
调用堆栈中的下一个中间件。
如果当前中间件没有终结请求-响应循环,则必须调用 next() 方法将控制权交给下一个中间件,否则请求就会挂起。

Express 应用可使用如下几种中间件:

应用级中间件
路由级中间件
错误处理中间件
内置中间件
第三方中间件
使用可选则挂载路径,可在应用级别或路由级别装载中间件。另外,你还可以同时装在一系列中间件函数,从而在一个挂载点上创建一个子中间件栈。

应用级中间件

应用级中间件绑定到 app 对象 使用 app.use() 和 app.METHOD(), 其中, METHOD 是需要处理的 HTTP 请求的方法,例如 GET, PUT, POST 等等,全部小写。例如:

var app = express();

// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});

// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它
app.use('/user/:id', function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

// 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
  res.send('USER');
});

下面这个例子展示了在一个挂载点装载一组中间件。

// 一个中间件栈,对任何指向 /user/:id 的 HTTP 请求打印出相关信息
app.use('/user/:id', function(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  next();
}, function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

作为中间件系统的路由句柄,使得为路径定义多个路由成为可能。在下面的例子中,为指向 /user/:id 的 GET 请求定义了两个路由。第二个路由虽然不会带来任何问题,但却永远不会被调用,因为第一个路由已经终止了请求-响应循环。

// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
  console.log('ID:', req.params.id);
  next();
}, function (req, res, next) {
  res.send('User Info');
});

// 处理 /user/:id, 打印出用户 id
app.get('/user/:id', function (req, res, next) {
  res.end(req.params.id);
});

如果需要在中间件栈中跳过剩余中间件,调用 next('route') 方法将控制权交给下一个路由。 注意: next('route') 只对使用 app.VERB() 或 router.VERB() 加载的中间件有效。

// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
  // 如果 user id 为 0, 跳到下一个路由
  if (req.params.id == 0) next('route');
  // 否则将控制权交给栈中下一个中间件
  else next(); //
}, function (req, res, next) {
  // 渲染常规页面
  res.render('regular');
});

// 处理 /user/:id, 渲染一个特殊页面
app.get('/user/:id', function (req, res, next) {
  res.render('special');
});

路由级中间件

路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()。

var router = express.Router();

路由级使用 router.use() 或 router.VERB() 加载。

上述在应用级创建的中间件系统,可通过如下代码改写为路由级:

var app = express();
var router = express.Router();

// 没有挂载路径的中间件,通过该路由的每个请求都会执行该中间件
router.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});

// 一个中间件栈,显示任何指向 /user/:id 的 HTTP 请求的信息
router.use('/user/:id', function(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  next();
}, function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

// 一个中间件栈,处理指向 /user/:id 的 GET 请求
router.get('/user/:id', function (req, res, next) {
  // 如果 user id 为 0, 跳到下一个路由
  if (req.params.id == 0) next('route');
  // 负责将控制权交给栈中下一个中间件
  else next(); //
}, function (req, res, next) {
  // 渲染常规页面
  res.render('regular');
});

// 处理 /user/:id, 渲染一个特殊页面
router.get('/user/:id', function (req, res, next) {
  console.log(req.params.id);
  res.render('special');
});

// 将路由挂载至应用
app.use('/', router);

错误处理中间件

错误处理中间件有 4 个参数,定义错误处理中间件时必须使用这 4 个参数。即使不需要 next 对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。

错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,而不是 3 个,其签名如下: (err, req, res, next)。

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

请参考 错误处理 一章了解更多关于错误处理中间件的内容。

内置中间件

从 4.x 版本开始,, Express 已经不再依赖 Connect 了。除了 express.static, Express 以前内置的中间件现在已经全部单独作为模块安装使用了。请参考 中间件列表。

express.static(root, [options])

express.static 是 Express 唯一内置的中间件。它基于 serve-static,负责在 Express 应用中提托管静态资源。

参数 root 指提供静态资源的根目录。

可选的 options 参数拥有如下属性。

属性    描述    类型    缺省值
dotfiles    是否对外输出文件名以点(.)开头的文件。可选值为 “allow”、“deny” 和 “ignore”    String    “ignore”
etag    是否启用 etag 生成    Boolean    true
extensions    设置文件扩展名备份选项    Array    []
index    发送目录索引文件,设置为 false 禁用目录索引。    Mixed    “index.html”
lastModified    设置 Last-Modified 头为文件在操作系统上的最后修改日期。可能值为 true 或 false。    Boolean    true
maxAge    以毫秒或者其字符串格式设置 Cache-Control 头的 max-age 属性。    Number    0
redirect    当路径为目录时,重定向至 “/”。    Boolean    true
setHeaders    设置 HTTP 头以提供文件的函数。    Function

下面的例子使用了 express.static 中间件,其中的 options 对象经过了精心的设计。

var options = {
  dotfiles: 'ignore',
  etag: false,
  extensions: ['htm', 'html'],
  index: false,
  maxAge: '1d',
  redirect: false,
  setHeaders: function (res, path, stat) {
    res.set('x-timestamp', Date.now());
  }
}

app.use(express.static('public', options));
每个应用可有多个静态目录。

app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));

更多关于 serve-static 和其参数的信息,请参考 serve-static 文档。

第三方中间件

通过使用第三方中间件从而为 Express 应用增加更多功能。

安装所需功能的 node 模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。

下面的例子安装并加载了一个解析 cookie 的中间件: cookie-parser

$ npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');

// 加载用于解析 cookie 的中间件
app.use(cookieParser());

项目源码的github地址(厚脸皮求个star★)


codercao
395 声望19 粉丝