总结自:Coderwhy老师的nodejs课程。
express项目创建有两种方式:
方式一:通过express提供的脚手架,直接创建一个应用的骨架;
// 安装express-generator
// 安装脚手架
npm install -g express-generator
// 创建项目
express express项目名
// 安装依赖
npm install
// 启动项目
node bin/www
生成的目录
.
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.pug
├── index.pug
└── layout.pug
方式二:从零搭建自己的express应用结构;
npm init -y
这里我还是建议第二种方式来配置我们的express。
中间件
其实学习express就是学习中间件。
什么是中间件
中间件的本质是传递给express的一个回调函数; 这个回调函数接受三个参数:
- 请求对象(request对象);
- 响应对象(response对象);
- next函数(在express中定义的用于执行下一个中间件的函数);
- 并且中间件不调用next(),那么它将只会调用第一个匹配的中间件。
中间件可以做哪些事情
- 执行任何代码;
- 更改请求(request)和响应(response)对象;
- 结束请求-响应周期(返回数据);
- 调用栈中的下一个中间件;
如果当前中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起。 如果next方法传递了参数,那么它将不会调用下一个匹配到的中间件,而是直接匹配到传递了接受错误的中间件。如果没有找到接受错误的中间件,那么它将直接终止程序。
中间件这么牛逼,那如何使用它呢?
中间件的使用方式
- 最普通的中间件
通过
app.use((req, res, next) => {})
来使用。
app.use((req, res, next) => {
console.log("注册了第01个普通的中间件~");
next();
});
- path匹配中间件
通过
app.use(路径, (req, res, next) => {})
来使用。
// 路径匹配的中间件
app.use('/index', (req, res, next) => {
console.log("index middleware 01");
});
- path和method匹配中间件
通过
app.[mothod](路径, (req, res, next) => {})
来使用。
app.get('/index', (req, res, next) => {
console.log("index path and method middleware01");
});
app.post('/login', (req, res, next) => {
console.log("login path and method middleware01");
})
- 注册多个中间件 每个注册中间件的方式都可以注册多个中间件。
app.get("/index", (req, res, next) => {
console.log("index path and method middleware 02");
next();
}, (req, res, next) => {
console.log("index path and method middleware 03");
next();
}, (req, res, next) => {
console.log("index path and method middleware 04");
res.end("index page");
});
不管中间件是什么类型的,他都是从前往后匹配的,没有调用next方法,即使匹配到后面的中间件也不会执行。如果一个匹配到的中间件结束了响应,调用了next他会继续执行下一个中间件,没有调用next他将结束响应。
特别需要注意的是,当多个匹配的中间件同时设置了响应和调用了next,该请求响应的结果为最先发出的响应。并且终端会报错。一般我们也不会这样干,只会在最后一个被匹配到的中间件设置响应。
app.use((req, res, next) => {
console.log("common middleware01");
next();
})
// 路径匹配的中间件
app.use('/home', (req, res, next) => {
console.log("home middleware 01");
next()
res.send("=====")
});
// 中间插入了一个普通的中间件
app.use((req, res, next) => {
console.log("common middleware02");
res.send("+++++")
next();
})
app.use('/home', (req, res, next) => {
console.log("home middleware 02");
res.send("----------")
});
上面事例,最终响应的结果是+++++。
解析请求传递的参数
解析params参数
我们直接通过req.params
就可以获取到。
app.get('/index/:id/:name', (req, res, next) => {
console.log(req.params);
res.end("参数获取成功~");
})
解析query参数
我们直接通过req.query
就可以获取到。
app.get('/login', (req, res, next) => {
console.log(req.query);
res.end("用户登录成功~");
})
解析请求中的json格式数据
调用express中内置的方法express.json()
。该方法将返回一个函数。并将解析后的参数放在req.body
上。
app.use(express.json())
app.post("/login", (req, res, next) => {
console.log("login-body", req.body)
// 注意end方法只能返回字符串类型或者是buffer类型
res.end(JSON.stringify(req.body))
next()
})
解析请求中的x-www-from-urlencoded格式数据
调用express中内置的方法express.urlencoded({ extended: true })
。该方法将返回一个函数。并将解析后的参数放在req.body
- 如果extended为true,表示它将采取第三方的qs库解析请求的body
- 如果extended是false,表示他将采取node的querystring模块解析请求的body
app.use(express.urlencoded({
extended: true
}))
app.post("/register", (req, res, next) => {
console.log("register-body", req.body)
res.end(JSON.stringify(req.body))
next()
})
解析请求中的form-data格式数据
这个express没有提供内置的中间件。我们需要安装multer
库来帮助我们解析。不要将它作为全局中间件来使用,上传文件和费文件的form-data格式的数据,可能会产生冲突。
解析非文件的表单数据
const multer = require("multer");
const upload = multer();
// 将它作为全局中间件时,它将会产生冲突,不能正确的解析文件上传。所以不要将他作为全局中间件使用
// app.use(upload.any())// 然后他就会将form表单非文件的数据解析成对象,绑定到req.body上
app.post('/login', upload.any(), (req, res, next) => {
console.log("login", req.body);
res.end("用户登录成功~")
});
解析文件的表单数据
我们可以指定上传的文件名和后缀。也可以让系统自动分配文件名。
- any方法不能作为全局的中间件,他会和single,array等方法冲突。
- array方法第二个参数可以指定上传文件的最大数量。
- fields方法表示传递不同字段的多文件。
[
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 8 }
]
// 方式一
// 自定义上传文件的文件名和格式
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({
storage
});
// 方式二
// 只提供文件上传的输出位置,文件名随机。
const upload = multer({
dest: './uploads/'
});
// 上传多个文件。注意文件上传的name都必须为file
app.post('/uploadmore', upload.array('file'), (req, res, next) => {
console.log(req.files);
res.end("文件上传成功~");
});
// 上传单个文件。注意文件上传的name都必须为file
app.post('/uploadsingle', upload.single('file'), (req, res, next) => {
console.log(req.file);
res.end("文件上传成功~");
});
// 上传多个文件。并且可以指定不同的字段名。
app.post('/upload', upload.fields([
{
name: "file"
},{
name: "avatar"
}
]), (req, res, next) => {
console.log(req.files);
res.end("文件上传成功~");
});
响应请求
详细请求,请访问 expressjs.com/zh-cn/api.h…
- end方法。类似于http中的response.end方法,用法是一致的。
res.end(返回的数据); // 只能是字符串或者buffr类型
- json方法。json方法中可以传入很多的类型:object、array、string、boolean、number、null等,它们会被转换成json格式返回;
res.json(返回的数据)
- status方法。用于设置状态码:
res.status(200);
- get方法。获取请求头中指定的字段
res.get("Content-Type");
- type方法。用来设置返回值mime类型的。
res.type("application/json");
express路由
为什么要使用路由?
如果我们将所有的代码逻辑都写在app中,那么app会变得越来越复杂:
-
一方面完整的Web服务器包含非常多的处理逻辑;
-
另一方面有些处理逻辑其实是一个整体,我们应该将它们放在一起:比如对users相关的处理
-
获取用户列表;get
-
获取某一个用户信息;get/:id
-
创建一个新的用户;post (body携带参数)
-
删除一个用户;delete/:id
-
更新一个用户;patch/:id
-
创建路由
通过express.Router()
来创建一个路由实例。
// userRouter.js。用户相关的路由
const express = require('express');
const userRouter = express.Router();
userRouter.get('/', (req, res, next) => {
res.json(["zh", "llm"]);
});
userRouter.get('/:id', (req, res, next) => {
res.json(`${req.params.id}用户的信息`);
});
userRouter.post('/', (req, res, next) => {
res.json("create user success~");
});
module.exports = userRouter;
// index.js
const express = require('express');
const userRouter = require('./router/users.js');
const app = express();
// 使用路由,并且统一路由路径。
app.use("/users", userRouter);
app.listen(8000, () => {
console.log("路由服务器启动成功~");
});
部署静态资源
Node也可以作为静态资源服务器,并且express给我们提供了方便部署静态资源的方法;
通过express.static()
返回一个中间件。
- 向
express.static
函数提供的路径相对于您在其中启动node
进程的目录。如果从另一个目录运行 Express 应用程序,那么对于提供资源的目录使用绝对路径会更安全。 - 可以为静态文件创建虚拟路径。路径并不实际存在于文件系统中。
app.use("/static", express.static(path.resolve(__dirname, 'uploads')));
上面表示可以访问具有 /static
路径前缀的 uploads
目录中的文件。访问的路径不需要带uploads路径,直接写uploads文件夹下的文件和文件夹即可。
const express = require('express');
const path = require("path")
const app = express();
// 部署静态文件,我们就可以访问项目下的static中的任何文件了
app.use(express.static(path.resolve(__dirname, "static")));
app.listen(8000, () => {
console.log("路由服务器启动成功~");
});
错误处理
错误处理中间件始终采用四个自变量。必须提供四个自变量,以将函数标识为错误处理中间件函数。即使无需使用 next
对象,也必须指定该对象以保持特征符的有效性。否则,next
对象将被解释为常规中间件,从而无法处理错误。
调用next(new Error("传递错误信息"))
。如果next中传入参数,那么将是错误参数,通过app.use((err, req, res, next) => {})
来对错误做统一处理。
我们会将错误信息定义成常量,然后做处理。
app.use((err, req, res, next) => {
let status = 400;
let message = "";
switch(err.message) {
case 错误信息对应的常量:
message = "错误信息对应的常量";
break;
case 错误信息对应的常量:
message = "错误信息对应的常量"
break;
default:
message = "NOT FOUND~"
}
res.status(status);
res.json({
errCode: status,
errMessage: message
})
})
需要注意的是,这个中间件主要是为了处理错误,所以需要获取到错误信息,所以应该充当最后一个中间件函数。
express源码分析
调用express(),他其实就是调用createApplication(),这个函数返回一个app。并且这个app就是一个中间件调用函数。
var app = function(req, res, next) { app.handle(req, res, next); };
app.listen()函数调用了一下原生的createServer().listen()方法,并传入参数。
app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); };
调用app.use()。通过判断,传入的中间件是否为一个函数,如果是,则会将所有中间件传入到一个fns数组中。不是则将报错。
var fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) { throw new TypeError('app.use() requires a middleware function') }
然后遍历这个fns数组,调用router.use()。然后再其中创建一个layer对象,并且将fn传递进去。然后将layer对象加入到一个stack数组中。
var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer);
当用户发送请求,我们可以通过app.listen()方法可以看出底层调用app.handle(),其中又调用router.handle()。其中内部不断判断匹配到的中间件函数,然后调用layer.handle_request。
if (route) { return layer.handle_request(req, res, next); }
然后内部调用中间件函数。并且传入req, res, next。然后在router.handle()中调用next方法。继续判断下一个中间件。
Layer.prototype.handle_request = function handle(req, res, next) { var fn = this.handle; if (fn.length > 3) { // not a standard request handler return next(); } try { fn(req, res, next); } catch (err) { next(err); } };