我是一名学前端的学生,为什么跑来学后端,学node.js呢?我们学前端的知识点广,而后端的知识点则更深。以后出去工作,前后端是要搭配着工作的,我们不学着点后端知识(比如前后端的网络交互),那以后项目出现点什么BUG,后端都把锅摔给前端咋办?哈哈。不过我学后端,主要还是为了开发一些属于自己的小东西,不能总是搭配一些静态页面吧,那多无聊啊。程序员都想有属于自己能上线的小东西,我就是奔着这个来的。而express框架是node.js中相对较全的库,学会它足够满足我们的小项目。好啦,开始学习之路。
如何在node.js项目中引入express框架?
在vscode终端输入下面命令:
cnpm install express --save
a.看个服务器简单的get请求:
var express=require("express");//引入express框架
var app=express();//框架实例
app.listen(8080);//监听8080端口
app.get("/",function(req,res){
res.send("this is 首页");//返回给前端浏览器
});
console.log("node.js server is starting");
b.启动服务器:
如果不清楚nodemon命令可以看我这篇博客。
c.在浏览器访问该服务器:
你看一个简单的服务器就这样搭建,这就是express框架的优势。
注意:app.send()这个方法不仅仅可以发送字符串,也可以发送对象(数组也是对象),且它会自动帮你转换为JSON格式,不需要你再显式方式转换。
由于我们上面搭配的服务器只写了根路由得响应,那么我们在浏览器访问其它路由会怎样?
那我是怎么会知道这些框架的方法的呢?你是否还不知道app.get()中的回调函数的两个参数是什么?它们都是一个对象,可以操作所有的请求与响应,req处理来自浏览器的请求,res处理响应回给浏览器
d.查看express官方文档学习req与res(学会利用资源)
通过官方文档学习才是最准确的。这里进入文档。
上面的服务器就是收到了来自浏览器8080端口的根路由的请求,然后触发了app.get("/",function())方法,然后通过app.send()响应了浏览器。更多的学习还是得靠自己看文档实际操作噢。
配置路由是搭建服务器的重中之重,因为一个网站的每个功能每一个页面都对应的一个路由,比如一个视频网站,每一个视频都得有一个唯一的路由,那上千个视频你就得有上千个路由,你如何配置,像下面这样?
app.get("/movies/zhanlang",func());
app.get("/movies/baxianguohai",func());
app.get("/movies/huabu",func());
.......
每个视频都在/moives路由下,只是下一个路由对应的是片名,上面这种做法就是把一个路由给写死了。每个路由都只能配置固定得请求。
我们应该像下面这样配置路由参数:
var express=require("express");//引入express框架
var app=express();
app.listen(8080);//监听8080端口
app.get("/movies/:movesName",function(req,res){
res.send("打开某某视频");
})
console.log("node.js server is starting");
看图得结果:
我们看上面代码有什么区别吗?
区别在于在前面加了个 “:” 号,这样只要前面得路由匹配,后面无论是什么都可以被访问。
那我们在服务器如何通过请求路由获得用户想观看的视频名呢?
var express=require("express");//引入express框架
var app=express();
app.listen(8080);//监听8080端口
app.get("/movies/:movieName",function(req,res){
var movieName=req.params.movieName;
res.send(`用户想看:${movieName}视频`);
})
console.log("node.js server is starting");
看图解释:
为什么我知道是使用req.params…来获得路由的值呢?还是查看官方文档,既然是关于req的,那肯定在上面我放出来的Request中查看学习即可。
看浏览器是否获得该路由对应值。
既然能拿到路由参数,那我们就可以只是用一条动态路由来匹配/movies路由下的请求啦,再通过获得的路由参数找到对应的资源返回给客户端即可。
还可以像下面这样匹配更复杂的路由:
app.get("/movies/:movieName/user/:loginName",function(req,res){
//.......
})
这种路由只要是:
localhost/movies/.../user/...
这类型的请求都可以匹配。
当然,你还可以在app.get()的第一个参数使用正则表达式来配置路由,原理一样。
说白了,查询字符串也是对路由的的相关操作。
查询什么字符串?就看CSDN的写作页面的地址:
注意到地址中 “?” 后面的字符串了吧,它是一对键值对。我们怎样把它取出来呢?
看下面代码:
var express=require("express");//引入express框架
var app=express();
app.listen(8080);//监听8080端口
app.get("/movies/:movieName",function(req,res){
var movieName=req.query;
console.log(movieName);
res.send(`用户想看视频`);
})
console.log("node.js server is starting");
下面给地址添加上:
var express=require("express");//引入express框架
var app=express();
app.listen(8080);//监听8080端口
app.get("/movies/",function(req,res){
var movieName=req.query;
console.dir(movieName);
res.send(`看看:${movieName.find}`);
})
console.log("node.js server is starting");
服务器能打印结果吗?
浏览器呢?
小结:req.query属性返回一个对象,通过对象属性获得键值对的值。
a. post请求一般用于提交数据,比如表单的提交等等。
b. 可能很多人都会在前端提交表单,那我们这里就用postman来模拟表单的提交吧,同时可以学学怎么用postman工具。
c. 如果你也想用postman模拟,须先下载postman软件或者在chorme浏览器添加postman插件。
d. post请求比get请求多了一点,需先引入比较流行的库body-parser,专门用于处理post请求,想要更多了解这个库的内容请点击这里。
下面在vscode终端引入该库:
cnpm install body-parser --save
写一个post的服务器:
var express=require("express");//引入express框架
var bodyParser=require("body-parser");
var app=express();
app.use(bodyParser.urlencoded({extended:false}));//添加通用的JSON和URL编码的解析器作为顶级中间件,该中间件将解析所有传入请求的主体。
app.use(bodyParser.json());
app.listen(8080);//监听8080端口
app.post("/",function(req,res){
console.log(req.body);//需要知道的是post请求将所有内容放进body里了
res.send(req.body);
})
console.log("node.js server is starting");
在不利用表单的情况下,使用postman模拟发起post请求:
查看服务器的响应:
postman模拟表单发起post请求成功。
那我如果想在postman的post请求发送json数据怎么弄呢?
明显这也是可以的。为什么发送这两种不同格式的请求都可以得到服务器的相应呢?因为我们添加了两个中间件。
app.use(bodyParser.urlencoded({extended:false}));//表单数据使用
app.use(bodyParser.json());//json数据使用
这两者前提都需要引入body-parser库。
a.我们先引入专门处理文件上传的库:
cnpm install --save multer
这个库非常流行,可处理多文件处理,点击这里进入详细有文档学习。
b.现在我们在服务器创建一个form.html文件,将它发送回给浏览器,用于文件上传:
c.先尝试服务端能否给浏览器返回form.html文件:
var express=require("express");//引入express框架
var bodyParser=require("body-parser");
var fs=require("fs");//引入流来读取form.html
var app=express();
app.use(bodyParser.urlencoded({extended:false}));//添加通用的JSON和URL编码的解析器作为顶级中间件,该中间件将解析所有传入请求的主体。
app.use(bodyParser.json());
app.listen(8080);//监听8080端口
app.get("/form",function(req,res){
var form=fs.readFileSync("./form.html",{encoding:"utf8"});//同步读取文件
res.send(form);
})
console.log("node.js server is starting");
这里服务器只是写了浏览器向服务器请求页面,还没有写post请求,因此浏览器还不能上传文件
d. 在浏览器发起请求form.html:
很明显浏览器能得到服务器传回来的form.html文件。
e.接着给服务器添加处理上传文件方法:
var express=require("express");//引入express框架
var fs=require("fs");
var multer=require("multer");//引入中间件
var app=express();
app.listen(8080);//监听8080端口
app.get("/form",function(req,res){
var form=fs.readFileSync("./form.html",{encoding:"utf8"});//读取文件
res.send(form);
})
var upload=multer({dest:'uploads/'});//指明上传的文件放在服务器的uploads目录下,如果没有该目录会自动创建
app.post("/upload",upload.single('logo'),function(req,res){//第二个参数表明上传单个文件,字符串“logo”,对应form表单的input标签的name属性
res.send("上传成功
");
})
console.log("node.js server is starting");
f. 测试一下吧,访问localhost/form:
g. 点击提交后的页面:
h. 查看服务器uploads目录下是否有上传的图片:
uploads目录下的确是多了一个文件,但是文件名是一串长长的字符串,怎么证明这就是那个文件呢?
i. 查看multer文档是否能获得文件原名:
j. 修改服务器代码:
var express=require("express");//引入express框架
var fs=require("fs");
var multer=require("multer");//引入中间件
var app=express();
app.listen(8080);//监听8080端口
app.get("/form",function(req,res){
var form=fs.readFileSync("./form.html",{encoding:"utf8"});//读取文件
res.send(form);
})
/*这里用下面的storage来代替了 var upload = multer({ dest: 'uploads/' })中的dest,storage是控制文件上传更详细的操作,通过它可以命名文件的名称*/
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')//同样是指定存储的目录
},
filename: function (req, file, cb) {//指定文件存储的文件名
cb(null, file.originalname)
}
})
var upload = multer({ storage: storage })
app.post("/upload",upload.single('logo'),function(req,res){//第二个参数表明上传单个文件,字符串“logo”,对应form表单的input标签的name属性
res.send("上传成功
");
})
console.log("node.js server is starting");
k. 重新上传一张图片,查看文件名:
有与源主机同样的名称就可以明确图片了。这些都可以在multer文档中学习。可以尝试多个文件上传。
其实,在返回form.html文件可以改进一句代码:
app.get("/form",function(req,res){
res.sendFile(__dirname+/form.html);//__dirname是node.js的全局属性,是指“当前目录”
})
sendFile()是express框架自带的方法,不需引入fs的流操作库。
我们在第6点学习的从服务器返回的form.html模板是静态的,如果我们想在服务器中返回form.html时就插入了数据库中的数据给模板,我们称只是动态模板,那我们如何实现呢?
a.在这里我们使用ejs模板引擎。可以嵌入动态数据
cnpm install ejs --save
var express=require("express");//引入express框架
var ejs=require("ejs");
var app=express();
app.set('view engine','ejs');//设置模板引擎为ejs,
app.set('views','./views');//指定动态模板在当前目录下的views中
app.get("/form",function(req,res){
var data="我的模板数据来自服务器动态插入";
res.render('form',{data:data});//render()总是用于返回html文本,第一个参数时form.ejs文件,第二个是准备用于插入模板的数据
})
app.listen(8080);//监听8080端口
console.log("node.js server is starting");
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态模板引擎</title>
</head>
<body>
<h1><%= data %></h1>
</body>
</html>
其实就是html代码,只不过是在要插入动态数据的地方添加"<%= … %>"即可。
e.启动服务器,查看是否插入成功:
插入成功。
我们来试试插入更复杂的数据:数组
var express=require("express");//引入express框架
var ejs=require("ejs");
var app=express();
app.set('view engine','ejs');//设置模板引擎为ejs,
app.set('views','./views');//指定动态模板在当前目录下的views中
app.listen(8080);//监听8080端口
app.get("/form",function(req,res){
var data={name:"鸭绒",age:22,hobby:["swim","code","eat"]};
res.render('form',{data:data});//第一个参数时form.ejs文件,第二个是准备用于插入模板的数据
})
console.log("node.js server is starting");
重点看form.ejs模板的变化:
什么叫共享模板,就是一个模板在多个地方都要利用,为了不在每个页面写相同的html代码,那就可以创建一个模板,在各个地方直接引用就可以了。
比如:
为了达到共享效果,我们还需创建两个ejs文件:
新建message.ejs:
共用模板share.ejs:
看看form.ejs的变化:
要实现模板共享,只需在用到的模板使用“<%- include(’…ejs’ -%>)”即可
Express是一个路由和中间件web框架,它自身具有最小的功能:Express应用程序本质上是一系列中间件函数调用。所谓中间件就是在请求-响应之间,在发起请求后,一般会使用一系列中间件来处理,然后才响应给浏览器,中间件到下一个中间件是通过next来传递。
express中有五种类型中间件:
看看中间件是怎么使用的吧
var express=require("express");//引入express框架
var app=express();
app.use(function(res,req,next){//app.use()就是使用中间件的意思
console.log("first ok");
next();
})
app.get("/",function(req,res,next){//之前写的都省略了next,用next传递到下一个中间件
console.log("has response");
res.send("YES!");
})
app.listen(8080);//监听8080端口
console.log("node.js server is starting");
注意: app.get()函数中第二个参数也是一个中间件。
app.use()就是使用中间件函数,只要声明了app.use(),则每次发起请求都会先经过它的处理。next()相当于回调函数,假设在app.use()中没有使用next(),就不会到下一个中间件,也最终不会到app.get()中,则app.get()相当于没用了。当然,在任何一个中间件都可以使用res,req。可以在中间件就响应浏览器。
中间件详细学习文档请看这里。看得再多不如自己动手。
1.npm是一个全球最大的开源库,这里拥有很多可以供我们使用的库。我们可以通过下面的网址进去搜索想要学的库:
https://www.npmjs.com
2.package.json是一个项目的配置文件,定义了这个项目所需要的各种模块以及项目的配置消息(比如名称、版本等等)。
如何创建package.json:
cnpm init
3.为什么都在使用cnpm而不是npm呢?npm命令使用的是国外服务器,因此,下载包时会比较慢。可以使用一个淘宝镜像安装cnpm,cnpm服务器在中国,速度相对较快。
在终端运行命令可使得cnpm代替npm
npm install -g cnpm --registry=https://registry.npm.taobao.org
1.cnpm install body-parser --save :用于获取post提交的数据
加载包:
cnpm install body-parser --save
实例:
//views目录下的form.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>body-parser</title>
</head>
<body>
<form action="/login" method="POST">
用户名:<input type="text" name="username" />
<br/>
密 码:<input type="password" name="password" />
<br/>
<input type="submit" value="提交" />
</form>
</body>
</html>
var bodyParser=require("body-parser");
var ejs=require("ejs");//用于返回服务器的一个form.html文件,如果文件直接在浏览器则不需要这个中间库
var express=require("express");
var app=express();
app.engine("html",ejs.__express);
app.set("view engine","html");
app.use(bodyParser.urlencoded({extended:false}));//应用body-parser时需声明,用于处理表单数据
app.use(bodyParser.json());//应用body-parser时需声明,用于处理json数据
app.listen(8080);
app.get("/register",(req,res)=>{
res.render("form",{});
})
app.post("/login",(req,res)=>{
console.log(req.body.username,req.body.password);//username,password对应表单的name属性
res.send("登陆成功");
})
2.cnpm install --save multer :专门处理文件上传,可用于多文件上传
目录第六个内容已给出实例
3.cnpm install ejs --save :专门用于模板引擎库
加载包:
cnpm install ejs --save
各标签用法:
1.<% %>:控制流,用于包裹在模板中的js代码,比如if,for循环
2.<%= %>:用于绑定服务端提供的动态数据
3.<%- %>:将未转义值输出到模板中
实例:
form.ejs文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ejs</title>
</head>
<body>
<ul>
<% array.forEach((item)=>{ %>
<li><%=item%></li>
<% }) %>
</ul>
</body>
</html>
服务器文件:
const ejs=require("ejs");
const express=require("express");
var app=express();
app.get("/",(req,res)=>{
var array=["小三","小四","小五"];
ejs.renderFile("./form.ejs",{array:array},(err,data)=>{
if(err){
console.error(err);
}
res.send(data);
})
})
app.listen(8080);
上面方法也可以,但更建议使用res.render(),即本博客第七节的模板引擎使用的方法。
1.res.render()返回html时,设置:
app.engine("html",ejs.__express);
app.set("view engine","html");
var bodyParser=require("body-parser");
var ejs=require("ejs");
var express=require("express");
var app=express();
app.engine("html",ejs.__express);
app.set("view engine","html");
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
app.listen(8080);
app.get("/register",(req,res)=>{
res.render("form",{});
})
2.当res.render()返回ejs时,设置:
app.set('view engine','ejs');//设置模板引擎为ejs,
app.set('views','./views');//指定动态模板在当前目录下的views中
var bodyParser=require("body-parser");
var ejs=require("ejs");
var express=require("express");
var app=express();
app.set('view engine','ejs');//设置模板引擎为ejs,
app.set('views','./views');//指定动态模板在当前目录下的views中
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
app.listen(8080);
app.get("/register",(req,res)=>{
res.render("form",{});
})
4.对数据加密库:md5
加载包:
cnpm install md5 --save
实例:
var md5=require("md5");
var content="我是加密的字符串";
console.log(md5(content));
将要加密的信息直接使用md5()即可,更多信息查看这里。
5.对时间进行格式化:silly-datetime
加载包:
cnpm install silly-datetime --save
实例:
var silly_dateTime=require("silly-datetime");
var date=silly_dateTime.format(new Date(),"YYYY-MM-DD HH:mm");
console.log(date);
6.读写文件:fs
加载包:
cnpm install fs --save
方法:
1. fs.stat:检测时文件还是目录
2. fs.mkdir:创建目录
3. fs.writeFile:创建写入文件
4. fs.appendFile:追加文件
5. fs.readFile:读取文件
6. fs.readdir: 读取目录
7. fs.rename: 重命名
8. fs.rmdir :删除目录
9. fs.unlink:删除文件
实例:
var fs=require("fs");
fs.stat("./photos",(err,data)=>{
if(err){
console.error(err);
return;
}
console.log(data.isFile());
console.log(data.isDirectory());
})
fs.readFile("./photos/picture1.jpg",(err,data)=>{
if(err){
console.error(err);
return;
}
console.log(data);
})
想学习更多,请查看这里。
7.处理请求的url字符串:url
加载包:
cnpm install url --save
实例:
var url =require("url");
var api="https://www.baidu.com:8080/downloads/what?name=zhanlang";
console.log("protocol:",url.parse(api).protocol);
console.log("host:",url.parse(api).host);
console.log("hostname:",url.parse(api).hostname);
console.log("port:",url.parse(api).port);
console.log("pathname:",url.parse(api).pathname);
console.log("search:",url.parse(api).search);
console.log("path:",url.parse(api).path);
console.log("query:",url.parse(api).query);
8. cookie的处理:cookie-parser:
cookie的作用:
引入包:
cnpm install cookie-parser --save
设置cookie:
req.cookie(key,value,options)
options的可选项:
1.maxAge:有效时长,过期后自动删除
2.signed:是否加密,默认加密
3.expires:设置一个日期,过期时间
4.httpOnly:是否只允许在服务器端访问,如果设置了之后,客户端js无法获得cookie
5.path:设置哪些路由可以访问这些cookie,即共享
6.domain:设置哪些域名可以获取cookie数据
7.secure:当secure为true时,cookie在http协议无效,在https协议才有效
看看页面之间共享cookie:
var express=require("express");
var cookieParser=require("cookie-parser");
var app=express();
app.listen(8080);
app.use(cookieParser());//使用中间件
app.get("/login",(req,res)=>{//在登陆界面设置cookie
res.cookie("username","小三",{maxAge:1000*60});
res.send("设置成功");
})
app.get("/chat",(req,res)=>{//在聊天界面获取cookie
res.send(`聊天页面:${req.cookies.username}`);
})
下面从多个二级域名共享cookie:在aaa.yarong.com设置cookie,在bbb.yarong.com获取前面设置的cookie.
在电脑本地模拟不同域名解析到相同的ip地址上:
试试用aaa.yarong.com来访问刚刚搭建的服务器:
在未更改服务器前,尝试使用bbb.yarong.com/chat来访问,是否能获得cookie呢?
下面更改以下服务器配置:
var express=require("express");
var cookieParser=require("cookie-parser");
var app=express();
app.listen(8080);
app.use(cookieParser());//使用中间件
app.get("/login",(req,res)=>{//在登陆界面设置cookie
res.cookie("username","小三",{maxAge:1000*60,domain:".yarong.com"});
res.send("设置成功");
})
app.get("/chat",(req,res)=>{
res.send(`聊天页面:${req.cookies.username}`);
})