总结自: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); } };