// 1、导入 express 模块
const express = require('express')
// 2、创建 web 服务器实例
const app = express()
app.get('/user', (req, res, next) => {
// 发送 json 对象给客户端
res.send({ name: 'ahcheng', age: 18 })
})
app.post('/user', (req, res, next) => {
// 发送文本内容给客户端
res.send('hhhh')
})
// 3、调用 app.listen( 端口号, 启动服务器后调用的回调函数 ),启动服务器,
app.listen(80, ()=> {
console.log("express server running at http://127.0.0.1:80")
})
Express是一个路由和中间件的Web框架,它本身的功能非常少:**Express应用程序本质上是一系列中间件函数的调用;**express 是一串中间件工具链。
中间件是什么呢?中间件的本质是传递给express的一个回调函数;
这个回调函数接受三个参数:
中间件中可以执行哪些任务呢?
res.end 在 next 之前执行,并不会报错。因为它只代表服务器响应结束了,后续无法再进行 http 相关的操作。但是中间件并没有结束,所以后续的中间件能继续执行。代码不报错。
如果当前中间件功能没有结束请求-响应周期,则必须调用 next() 将控制权传递给下一个中间件功能,否则,请求
将被挂起。
如何将一个中间件应用到我们的应用程序中呢?
app/router.use
和app/router.methods
;
学习use的用法,因为methods的方式本质是use的特殊情况:
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log("common middleware 01");
next();
});
app.get('/home', (req, res, next) => {
console.log("home path and method middleware 01");
next();
})
app.get("/home", (req, res, next) => {
console.log("home path and method middleware 02");
next();
}, (req, res, next) => {
console.log("home path and method middleware 03");
next();
}, (req, res, next) => {
console.log("home path and method middleware 04");
res.end("home page");
});
app.listen(8000, () => {
console.log("express初体验服务器启动成功~");
});
// common middleware 01
// home path and method middleware 01
// home path and method middleware 02
// home path and method middleware 03
// home path and method middleware 04
客户端传递到服务器参数的方法常见的是5种:
body 数据都是流,我们肯定不能直接使用,所以需要解析。而且 body 数据肯定是要被使用的,所以我们可以在前面定义两个通用中间件,先将 body 数据解析后,方便后续中间件直接使用。
const express = require('express');
const app = express();
// 自己编写的json解析
app.use((req, res, next) => {
// 判断请求头
if (req.headers["content-type"] === 'application/json') {
req.on('data', (data) => {
// 将请求体 body 中数据从流转换成 json 字符串,再解析 json 为对象
const info = JSON.parse(data.toString());
// 给 request 对象中添加一个 body 属性,填入转换好的数据对象,方便后续中间件直接使用
req.body = info;
})
req.on('end', () => {
next();
})
} else {
next();
}
})
app.post("/login", (req, res, next) => {
console.log(req.body); // { name: 'zs', age: 18 }
res.end("请求成功")
})
app.listen(8000, () => {
console.log("express初体验服务器启动成功~");
})
根据上面的例子,可见原生解析 json 是比较麻烦的,所以我们一般是使用库。
解析 json:使用库 body-parser。
**express.json()**
app.use(express.json()) // 一句代码就行,简洁太多了
另外 express 解析后,会给请求对象 req 添加一个 body 属性,并且会将解析好的数据放入其中。
urlencoded 格式也就是请求体 body 中数据为 Content-Type: x-www-form-urlencoded
。
x-www-form-urlencoded 纸面翻译即所谓 url 格式的编码,是 post 的默认 Content-Type。
get 请求的数据一般是拼接在 url 后面请求的,像这样 username=tom&pwd=123,这样的格式叫查询参数。
x-www-form-urlencoded 格式也长这样,所以说是 url 格式的编码。不同点在于,“查询参数”不是添加到 url 后面,而是添加在 post 请求的 body 里。
解析 urlencoded 格式的数据,express 提供了 urlencoded
方法,并且接收参数。
extended:
app.use(express.urlencoded({extended: true}));
form-data 格式可以传递一般数据,但是通常它是用来上传文件的。
手动解析它也是非常的麻烦,express 没有内置解析的方法,但是 express 官方提供了一个第三方库:multer
安装:npm i multer
multer 实例提供 any 方法可以解析非文件类型数据,它返回一个函数,可直接作为中间件使用。
const express = require('express');
const multer = require("multer")
const app = express();
const uploader = multer()
app.post("/login", uploader.any(), (req, res, next) => {
console.log(req.body); // [Object: null prototype] { name: 'zs', age: '18' }
res.end("请求成功")
})
app.listen(8000, () => {
console.log("express初体验服务器启动成功~");
})
const path = require('path');
const express = require('express');
const multer = require('multer');
const app = express();
// 设置文件的存储信息,disk 表示存储到磁盘中
const storage = multer.diskStorage({
destination: (req, file, cb) => { // 指定存储目录
cb(null, './uploads/');
},
filename: (req, file, cb) => { // 指定存储的文件名
cb(null, Date.now() + path.extname(file.originalname)); // 时间戳+后缀
}
})
const upload = multer({
// dest: './uploads/' // 不详细设置存储信息,直接存在指定目录
storage
});
// 应用中间件,完成存储。
// 单个文件:upload.single( form-data中文件的 key )
// 多个文件:upload.array( key ) 注意:多个文件上传要求使用同一个key
app.post('/upload', upload.array('pic'), (req, res, next) => {
// 单个文件信息保存在 file 中,上传多个文件的信息这是保存在 files 中
console.log(req.files); // 查看文件存储信息
res.end("文件上传成功~");
});
app.listen(8000, () => {
console.log("form-data解析服务器启动成功~")
});
// [
// {
// fieldname: 'pic',
// originalname: '@-阿莘-04.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: './uploads/',
// filename: '1664879117987.jpg',
// path: 'uploads\\1664879117987.jpg',
// size: 224435
// }
// ]
如果我们希望将请求日志记录下来,那么可以使用express官网开发的第三方库:morgan
安装:npm i morgan
const fs = require('fs');
const express = require('express');
const morgan = require('morgan');
const app = express();
const writerStream = fs.createWriteStream('./logs/access.log', {
flags: "a+"
})
// 指定日志输出格式,并且以流的形式输出
app.use(morgan("combined", {stream: writerStream}));
app.get('/home', (req, res, next) => {
res.end("Hello World");
})
app.listen(8000, () => {
console.log("express初体验服务器启动成功~");
});
什么是动态参数呢?
比如请求一个用户的个人信息/user/它的id
,这个 id 就是动态参数 params。中间间在路径匹配的时候需要使用:key
和动态路由一样的方式接收。
通过req.params
对象,可以访问到 URL 中,通过:
声明的动态参数:
// 访问: http://127.0.0.1/user/666
// : 标记 id 为动态参数
app.post('/user/:id', (req, res) => {
res.send(req.params) //{"id":"666"}
})
查询参数就是 url ? 后面的字符串。查询字符串的形式为:?参数名=参数值&参数名=参数值
通过 req.query
对象,可以访问到客户端通过查询字符串的形式,发送到服务器的数据:
// 访问:httt://127.0.0.1?name=zs&age=20
app.get('/', (req, res) => {
// 客户端使用 ?name=zs&age=20 这种查询字符串形式,发送到服务器的参数
// 可以通过 req.query 对象访问到,例如:req.query.name req.query.age
console.log(req.query) {} //{"name": "zs", "age": "20"}
})
更多响应的方式:https://www.expressjs.com.cn/4x/api.html#res
如果我们将所有的代码逻辑都写在app中,那么app会变得越来越复杂:
我们可以使用express.Router
来创建一个路由处理程序:
一个 Router 实例拥有完整的中间件和路由系统;因此,路由可以看成一个迷你应用程序(mini-app);
express.Router()
函数**创建路由实例。**在路由中设置中间件,然后在主路由中注册。主路由的 url 会和子路由的 url 拼接成完整的请求路径。
const express = require('express');
// 获取路由实例
const router = express.Router();
// 在路由中设置中间件
router.get('/', (req, res, next) => {
res.json(["why", "kobe", "lilei"]);
});
router.get('/:id', (req, res, next) => {
res.json(`${req.params.id}用户的信息`);
});
router.post('/', (req, res, next) => {
res.json("create user success~");
});
// 导出路由
module.exports = router;
const express = require('express');
const userRouter = require('./routers/users');
const app = express();
// 注册路由
app.use("/users", userRouter);
app.listen(8000, () => {
console.log("路由服务器启动成功~");
});
部署静态资源我们可以选择很多方式:一般都是使用 nginx,其实 Node 也可以作为静态资源服务器,并且 express给我们提供了方便部署静态资源的方法;
express.static(资源目录)
const express = require('express');
const app = express();
app.use(express.static('./build'));
app.listen(8000, () => {
console.log("静态资源服务器启动成功~");
});
**next 中传了参数,它就会传递给一个错误处理的中间件。**错误处理中间件,第一个参数 err,能获取到错误信息。
(err, req, res, next) => {}
const express = require('express');
const app = express();
// 错误信息定义成常量,统一管理
const USERNAME_DOES_NOT_EXISTS = "USERNAME_DOES_NOT_EXISTS";
const USERNAME_ALREADY_EXISTS = "USERNAME_ALREADY_EXISTS";
app.post('/login', (req, res, next) => {
// 加入在数据中查询用户名时, 发现不存在
const isLogin = false;
if (isLogin) {
res.json("user login success~");
} else {
// 没有错误中间件的时候,手动处理
// res.type(400);
// res.json("username does not exists~")
// 传递给错误中间件处理
next(new Error(USERNAME_DOES_NOT_EXISTS));
}
})
app.post('/register', (req, res, next) => {
// 加入在数据中查询用户名时, 发现不存在
const isExists = true;
if (!isExists) {
res.json("user register success~");
} else {
// res.type(400);
// res.json("username already exists~")
next(new Error(USERNAME_ALREADY_EXISTS));
}
});
app.use((err, req, res, next) => {
let status = 400;
let message = "";
// 统一管理了错误信息,使用时可以匹配响应错误信息
switch(err.message) {
case USERNAME_DOES_NOT_EXISTS:
message = "username does not exists~";
break;
case USERNAME_ALREADY_EXISTS:
message = "USERNAME_ALREADY_EXISTS~"
break;
default:
message = "NOT FOUND~"
}
// 响应错误信息和状态码
res.status(status);
res.json({
errCode: status,
errMessage: message
})
})
app.listen(8000, () => {
console.log("路由服务器启动成功~");
});