express框架
express 是一个web框架,也是npm的一个工具包,功能和http模块类似,但功能更加强大,开发服务端效率更高
基本使用步骤:
- 导入express框架
- 创建服务器实例对象
- 绑定事件
- 监听端口,启动服务器
//导入expressconst express = require('express');// 创建对象const app = express();//创建路由,req请求报文的封装对象,res响应报文app.get('/home', (req, res) => { // 如果请求的方法是get,并且url路径是/home,则执行 res.end('Welcome');})// 监听端口启动服务app.listen(3000, () => { console.log('服务启动');});路由
**路由:**确定如何响应客户端对特定端点的请求
app.<method>(path,(req, res) => {})//创建路由,req请求报文的封装对象,res响应报文//从上往下匹配,匹配到则不再往后匹配app.get('/home', (req, res) => { // 如果请求的方法是get,并且url路径是/home,则执行 res.end('Welcome /home');})
app.get('/', (req, res) => { res.end('Welcome /');});
app.post('/login', (req, res) => { res.end('Welcome /login');});
app.all('/test', (req, res)=>{ res.end('Welcome /test');});// 匹配所有路径和方法,可以放在最后进行404响应app.all('*', (req, res)=>{ res.end('404');});获取请求报文
app.get('/', (req, res) => { //express兼容原生操作 console.log(req.method);//请求方法 console.log(req.url);//请求url console.log(req.httpVersion);//http版本 console.log(req.headers);//请求头 //express封装方法 console.log(req.path);//请求路径 console.log(req.query);//查询字符串 console.log(req.ip);//用户ip console.log(req.get('host'));//获取特定请求头的属性值
res.send('Welcome /');});路由参数
路由参数指的是 URL 路径中的参数(数据),如多篇文章按1.html、2.html往后排,匹配路由参数从而无需写多个路由
获取路由参数:req.params.id
app.get('/:id.html',(req, res) => { res.send('id为' + req.params.id);});路由参数练习:
{ "singers": [ { "singer_name": "周杰伦", "singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp", "other_name": "Jay Chou", "singer_id": 4558, "id": 1 }, { "singer_name": "林俊杰", "singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000001BLpXF2DyJe2.webp", "other_name": "JJ Lin", "singer_id": 4286, "id": 2 }, { "singer_name": "G.E.M. 邓紫棋", "singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000001fNHEf1SFEFN.webp", "other_name": "Gloria Tang", "singer_id": 13948, "id": 3 } ]}const express = require('express');// 创建对象const app = express();//导入json文件const {singers} = require('./JSON/singers.json');
app.get('/singer/:id.html',(req, res) => { let {id} = req.params; let result = singers.find((item)=>{ if(item.id === Number(id)){ return true; } }); // console.log(result); if(!result){ res.send("404"); return; } res.send(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h2>${result.singer_name}</h2> <img src=${result.singer_pic}}></img> </body> </html> `);});
app.listen(3000, () => { console.log('服务启动');})响应设置
一般响应
app.get('/', (req, res)=>{ // 原生响应 res.statusCode = 200; res.statusMessage = 'OK'; res.setHeader('Content-Type', 'text/html;charset=UTF-8'); // express res.status(200); res.set('Content-Type', 'text/html'); res.send('你好');//自动添加字符集,不乱码 // 连贯操作 res.status(200).set('xxx','yyy').send('你好');});其它响应,一般不能多个同时成功响应
app.get('/', (req, res)=>{ // 302重定向 res.redirect('https://www.baidu.com/'); // 下载响应 res.download('../3.mp4'); // 响应JSON res.json({ a:1 }); // 响应文件内容 res.sendFile(__dirname + '/package.json');});中间件
中间件(Middleware)本质是一个回调函数
中间件函数可以像路由回调一样访问请求对象(request),响应对象(response)
**作用:**使用函数封装公共操作,简化代码
**类型:**全局中间件、路由中间件
每一个请求到达服务端后都会执行全局中间件函数 满足某个路由规则的请求会触发指定路由中间件函数
function middleware(req,res,next){//实现功能代码//执行next函数//调用next()执行完中间件函数后,继续执行路由中的回调函数next();}全局中间件
每一个请求到达服务端后都会执行全局中间件函数
app.use是专门解析中间件函数的方法,使用之后express会将http请求响应对象交给中间件函数,中间件函数处理完通过next()方法传递给下一中间件函数,直到返回响应数据
可以使用 app.use() 定义多个全局中间件
案例:
// 声明中间件function middleware1(req, res, next) { let {url, ip} = req; fs.appendFileSync(path.resolve(__dirname + '/1.log'), `${url} ${ip}\r\n`); // 中间件函数处理完请求后,通过next方法将http请求对象交给下一个中间件或路由,直到返回响应 next();}function middleware2(req, res, next) { let {url, ip} = req; console.log(url, ip); next();}// 使用app.use定义全局中间件,express将http请求对象交给中间件app.use(middleware1);app.use(middleware2);
app.get('/home', (req, res)=>{ res.send('首页');});app.get('/admin', (req, res)=>{ res.send('后台');});路由中间件
满足某个路由规则的请求会触发指定路由中间件函数
只需要对某一些路由进行功能封装,则使用路由中间件,可以设置多个中间件函数
app.get('/路径',`中间件函数1`,`中间件函数2`,(req,res)=>{});案例:
function checkCode(req, res, next){ if (req.query.code === '123'){ next();//继续执行路由回调 }else{ res.send('code参数错误'); }}app.get('/home', (req, res)=>{ res.send('首页');});// 放到需要的路由上app.get('/admin', checkCode, (req, res)=>{ res.send('后台');});app.get('/setting', checkCode, (req, res)=>{ res.send('设置');});静态资源中间件
express内置处理静态资源的中间件
app.use(express.static('<静态资源目录>'));设置后,所有静态资源都会根据静态资源目录去寻找,index.html、index.css等为默认打开的资源
果静态资源与路由规则同时匹配,从上而下谁先匹配谁就响应
一般用路由响应动态资源,如搜索结果等,用静态资源中间件响应静态资源,如html、css等
// 设置静态资源目录,会自动添加Mime类型app.use(express.static('../public-test'));获取请求体
express 可以使用 body-parser 包处理请求体,该包内置许多中间件来处理请求体,需要先npm安装npm i body-parser
包内常用中间件,当中间件解析完请求后会向 req 对象上添加 body 属性,里面包含了请求体对象
// 解析 json 格式的中间件var jsonParser = bodyParser.json()// 解析 querystring 格式的中间件var urlencodedParser = bodyParser.urlencoded({ extended: false })案例:
const bodyParser = require('body-parser');// 获取表单post提交的用户名和密码,get请求则显示登录页面app.get('/login', (req, res)=>{ // 响应登录页面 res.sendFile(__dirname + '/login.html');});// 获取 bodyParser 包中的中间件函数// 解析 json 格式的中间件// 自带的express.json()也能解析json格式var jsonParser = bodyParser.json() // express.json()// 解析 querystring 格式的中间件var urlencodedParser = bodyParser.urlencoded({ extended: false })app.post('/login', urlencodedParser, (req, res) => { // 当urlencodedParser中间件执行完毕,会向req对象上添加一个body属性 console.log(req.body);//{ username: '543', password: '345' } // 成功获取包含用户名和密码的对象 res.send('登录成功!');});防盗链
请求报文中的 referer 会携带当前访问资源的域名和端口,限制 referer 的值即可实现防盗链
app.use((req, res, next) => { // 获取请求头中的referer let referer = req.get('referer'); // referer不为空再去执行 if (referer) { // 解析referer let url = new URL(referer); // url.hostname 获取域名,判断是否是目标域名 if (url.hostname !== '127.0.0.1') { res.status(404).send('404 Not Found'); return; } } next();});路由模块化Router
Router 是一个完整的中间件和路由系统,相当于一个小型的 app 对象。
const router = express.Router();const app = express();const express = require('express');
const router = express.Router();// 相当于一个小型的app对象
router.get('/home', (req, res)=>{ res.send('首页');});
router.get('/search', (req, res)=>{ res.send('搜索');});
// 将路由暴露出去module.exports = router;const express = require('express');// 导入路由const homeRouter = require('./home.js');const adminRuter = require('./admin.js');
const app = express();// 使用路由app.use(homeRouter);app.use(adminRuter);
app.all('*', (req, res) => { res.send('404 Not Found');});
app.listen(3000, ()=>{ console.log('服务启动');});app.use可以添加一个路径参数,来给路由添加前缀
app.use('/', indexRouter);app.use('/users', usersRouter);// 路由中的路径都会自动添加这个前缀模板引擎ejs
模板引擎是分离用户界面和业务数据的一种技术,在许多语言都有的一种通用技术
EJS 是一个高效的 Javascript 的模板引擎,用于分离html和服务端js
const ejs = require('ejs');// 字符串let str1 = 'qx';let str2 = `${str1}chuckle`;console.log(str2);// qxchuckle
// 使用ejslet str = '<%= str %>chuckle';let result = ejs.render(str, {str:str1});console.log(result);// qxchuckle将 ejs 模板写在单独的文件再由fs读入
let str = fs.readFileSync('./01.html').toString();let [name,age] = ['chuckle',19]let result = ejs.render(str, {name,age});console.log(result);<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <h2><%= name %></h2> <h3><%= age %></h3></body></html>ejs列表渲染:
const arr = [1,2,3,4];let html = '<ul>';arr.forEach(item=>{ html += `<li>${item}</li>`;})html += '</ul>';console.log(html);//<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>const arr = [1,2,3,4];let html = `<ul> <% arr.forEach(item => { %> <li><%= item %></li> <% }) %></ul>`;let result = ejs.render(html, {arr});console.log(result);//<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>ejs条件渲染
let isLogin = true;let html = `<% if(isLogin) { %> <span>登陆</span><%}else{ %> <span>注册</span><%} %>`;let result = ejs.render(html, {isLogin});console.log(result);// <span>登陆</span>express使用ejs
使用 app.set() 设置express使用的模板引擎、设置模板文件存放位置
// 设置express使用的模板引擎app.set('view engine', 'ejs');// 设置模板文件存放位置app.set('views', path.resolve(__dirname +'/ejs'));然后就可以用res调用render方法响应渲染好的页面
// res.render('<模板文件名>','<数据>');let name = 'chuckle';// 在模板文件夹中创建home.ejsres.render('home', {name});完整案例:
const express = require('express');const path = require('path');
const app = express();// 设置express使用的模板引擎app.set('view engine', 'ejs');// 设置模板文件存放位置app.set('views', path.resolve(__dirname +'/ejs'));// 然后就可以用res调用render方法app.get('/', (req, res) => { // res.render('<模板文件名>','<数据>'); let name = 'chuckle'; // 在模板文件夹中创建home.ejs res.render('home', {name});});
app.listen(3000, ()=>{ console.log('服务启动');});express-generator
express-generator是一个node的自动化创建项目工具,类似于vue-cli,能快速构建express项目标准骨架
安装后会暴露一个全局命令:express
npm install -g express-generatorexpress -v //查看版本和帮助构建项目:
express -e <文件夹名称>// -e参数是添加ejs支持- app.js是项目主文件;
- views目录用于存放页面文件;
- routes目录用于存放路由文件;
- public用于存放静态文件;
- bin中的www是项目的启动文件;
文件上传
在index.js中添加两个路由,get显示表单,post接收上传的数据
// 显示表单router.get('/portrait', (req,res) => { res.render('portrait');});
// 处理文件上传router.post('/portrait', (req, res)=>{ res.send('上传成功');});新建 portrait.ejs 创建表单页面,对于文件上传,form表单必须添加 enctype="multipart/form-data" 属性
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <h3>文件上传</h3> <form action="/portrait" method="post" enctype="multipart/form-data"> 用户名:<input type="text" name="username"><br /> 头像:<input type="file" name="portrait"><br /> <button>提交</button> </form></body></html>处理文件上传需要使用 formidable 包,它是用于解析表单数据的 Node.js 模块,尤其是文件上传。
var express = require('express');var router = express.Router();// 导入formidableconst formidable = require('formidable');
/* GET home page. */router.get('/', function(req, res, next) { res.render('index', { title: 'Express' });});
// 显示表单router.get('/portrait', (req,res) => { res.render('portrait');});
// 处理文件上传router.post('/portrait', (req, res)=>{ // 创建表单对象 const form = formidable({ multiples: true, // 设置上传文件的保存目录 uploadDir: __dirname + '/../public/images', // 保持文件后缀 keepExtensions: true }); // 解析表单对象 form.parse(req, (err, fields, files) => { if (err) { next(err); return; } // fields存放除文件上传的一般表单提交 console.log(fields); // files保存文件上传 console.log(files); // res.json({ fields, files }); // 保存上传文件的存放路径,以后将此数据保存在数据库中 let url = '/images/' + files.portrait.newFilename; res.send(url);// 给用户返回url路径 });});
module.exports = router;lowdb包
在保存一些简单数据时可以使用 lowdb 包,在json中进行增删改查数据,推荐使用1.0.0版本
案例:
const low = require('lowdb')const FileSync = require('lowdb/adapters/FileSync')// 数据存储的josnconst adapter = new FileSync('db.json')// 获取db对象const db = low(adapter)// 初始化数据,也可以手动自定义初始化db.defaults({ posts: [], user: {} }).write()
// 增删改都需要最后调用write()// 往posts这个数组尾中添加元素db.get('posts') // push从尾部插入元素 .push({ id: 1, title: 'lowdb is awesome'}) .write()db.get('posts') // unshift从头插入元素 .unshift({ id: 2, title: 'lowdb is awesome'}) .write()
// 获取数据let value = db.get('posts').value();console.log(value);// 获取单条数据let single = db.get('posts').find({id: 1}).value();console.log(single);
// 删除所有匹配的数据,返回被删除的数据db.get('posts').remove({id: 1}).write();
// 更新数据,先获取再修改db.get('posts').find({id: 2}).assign({title: 'chuckle'}).write();案例-记账本
主要的一些文件
主要路由:
var express = require('express');var router = express.Router();
// 使用lowdb存储数据const low = require('lowdb')const FileSync = require('lowdb/adapters/FileSync')// 数据存储的josnconst adapter = new FileSync(__dirname + '/../data/db.json')// 获取db对象const db = low(adapter)
// 导入shortid为数据生成idconst shortid = require('shortid');
// 记账本页面路由router.get('/', function(req, res, next) { // 获取所有账单记录数据 let content = db.get('content').value(); // 将数据数组传递过去遍历渲染 res.render('index', {content});});
// 添加记录页面路由router.get('/add', function(req, res, next) { res.render('add');});
// 添加记录post接口路由router.post('/add', function(req, res, next) { // 生成唯一id let id = shortid.generate(); // 往db.josn写入数据 db.get('content').unshift({id:id, ...req.body}).write(); // 跳转到提示页 res.render('tip',{msg: '添加成功!', url: '/'});});
// 根据id删除数据,使用路由参数router.get('/delete/:id',(req, res) => { let id = req.params.id;// 获取路由参数 db.get('content').remove({id}).write(); res.render('tip',{msg: '删除成功!', url: '/'});});
module.exports = router;页面ejs:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>记账本</title> <link rel="stylesheet" href="/css/index.css" /> </head>
<body> <div class="content"> <div class="content-box"> <div class="title"> <h2>记账本</h2> </div> <div class="add"> <a href="/add">添加</a> </div> <div class="record-box"> <% content.forEach(item=>{%> <div class="record-item"> <div class="date"><%= item.date %></div> <div class="type <%= item.type==='支出'?'expenditure':'income'%>"> <%= item.type %> </div> <div class="record-body"> <div class="record-content"> <div class="matter"><%= item.matter %></div> <div class="account"><%= item.account %> 元</div> </div> <a href="/delete/<%= item.id %>" class="delete">删除</a> </div> </div> <%})%> </div> </div> </div> </body></html><!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>添加记录</title> <link rel="stylesheet" href="/css/add.css" /> </head>
<body> <div class="content"> <div class="content-box"> <div class="title"> <h2>添加记录</h2> </div> <div class="home"> <a href="/">账单列表</a> </div> <form action="/add" method="post"> <div class="form-item"> <label for="matter">事项</label> <input class="control" type="text" name="matter" id="matter" /> </div> <div class="form-item"> <label for="date">时间</label> <input class="control" type="date" name="date" id="date" /> </div> <div class="form-item"> <label for="type">类型</label> <select class="control" name="type" id="type"> <option selected="">支出</option> <option>收入</option> </select> </div> <div class="form-item"> <label for="account">金额</label> <input class="control" type="text" name="account" id="account" /> </div> <div class="form-item"> <label for="remark">备注</label> <textarea class="control" name="remark" id="remark"></textarea> </div> <hr /> <button>添加</button> </form> </div> </div> </body></html><!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <link rel="stylesheet" href="/css/tip.css" /> </head> <body> <div class="content"> <h3><%= msg %></h3> <a href="<%= url %>">返回</a> </div> </body></html>css:
body{ font-family:"Microsoft YaHei",微软雅黑; color: #363636;}*{ padding: 0; margin: 0; box-sizing: border-box; text-decoration: none;}.content{ max-width: 600px; margin: 0 auto;}.content-box{ display: flex; flex-direction: column; flex-wrap: wrap; align-content: center; margin: 0 10px;}.content-box>*{ width: 100%;}.title{ padding: 20px 0; border-bottom: 1px solid rgb(220, 220, 220);}.title h2{ font-size: 30px; font-weight: 500;}.add{ margin-top: 10px; padding-left: 20px;}.add a{ text-decoration: none;}.record-box{ margin-top: 10px; border-bottom: 1px solid rgb(220, 220, 220);}.record-item{ margin-bottom: 15px; border: 1px solid rgb(226, 226, 226); border-radius: 6px; -webkit-border-radius: 6px; -moz-border-radius: 6px; -ms-border-radius: 6px; -o-border-radius: 6px; position: relative;}.record-item>.date{ height: 34px; background: rgb(174, 209, 237); padding: 6px 12px; font-size: 14px; line-height: 22px;}.record-body{ display: flex; flex-direction: row; flex-wrap: wrap; padding: 10px 20px; justify-content: space-between; align-content: center;}.record-content{ display: flex; flex-direction: row; flex-wrap: wrap; justify-content: space-between; flex: 1;}.record-body>.delete{ padding-left: 10px; margin-left: 10px; border-left: 1px solid rgb(201, 201, 201); color: rgb(33, 70, 181);}.record-item>.type{ position: absolute; top: 5px; right: 10px; font-size: 14px; padding: 0 6px; height: 22px; line-height: 22px; color: #fff; border-radius: 4px; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px;}.type.expenditure{ background: rgb(241, 141, 158);}.type.income{ background: rgb(85, 194, 134);}body{ font-family:"Microsoft YaHei",微软雅黑; color: #363636;}*{ padding: 0; margin: 0; box-sizing: border-box; text-decoration: none;}hr{ margin: 20px auto; border: 0; border-top: 1px solid rgb(220, 220, 220);}.content{ max-width: 600px; margin: 0 auto;}.content-box{ display: flex; flex-direction: column; flex-wrap: wrap; align-content: center; margin: 0 10px;}.content-box>*{ width: 100%;}.home{ margin-top: 10px; padding-left: 10px;}.title{ padding: 20px 0; border-bottom: 1px solid rgb(220, 220, 220);}.title h2{ font-size: 30px; font-weight: 500;}.content-box form{ margin-top: 10px;}.form-item{ margin-bottom: 15px;}.form-item label{ display: block; height: 32px; line-height: 32px; font-size: 18px;}.form-item>*{ width: 100%;}.form-item>.control{ padding: 6px 12px; height: 36px; font-size: 16px; line-height: 36px; color: #363636; border: 1px solid #ccc; border-radius: 4px; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; transition: all 0.3s; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s; outline: none; font-family:"Microsoft YaHei",微软雅黑;}.form-item>.control:focus{ border-color: #66afe9; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);}.form-item>textarea.control{ padding: 8px 12px; height: 100px; line-height: 1; resize: none;}.content-box form>button{ width: 100%; padding: 6px 12px; margin-bottom: 15px; border: 1px solid rgb(57, 162, 204); background: rgb(37, 173, 204); border-radius: 4px; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; font-size: 17px; color: #fff; transition: all 0.3s; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s;}.content-box form>button:hover{ background: rgb(32, 153, 190);}body{ font-family:"Microsoft YaHei",微软雅黑; color: #363636;}*{ padding: 0; margin: 0; box-sizing: border-box; text-decoration: none;}.content{ margin: 0 auto; max-width: 300px; padding: 20px 30px; background: rgba(77, 190, 215, 0.6); margin-top: 20px; display: flex; align-items: center; flex-direction: row; flex-wrap: wrap; justify-content: space-between; border-radius: 4px; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px;}
评论