Node.js Web 框架 Express 的基本使用
Express 是 Node.js 中用的比较多的一个 Web 应用框架。使用 Express 你可以很方便的创建 HTTP 服务器和处理各种 HTTP 请求,无论是编写网站还是 API 服务,Express 都是一个不错的选择。
Express 是一个灵活可扩展的框架,Express 并不会集成所有的 HTTP 服务器功能,一些功能需要通过安装第三方中间件的方式实现。操作 Cookie 和 Session 之类的就需要通过安装中间件来实现。
创建一个简单的 HTTP 服务
初始化项目:
npm init -y
使用 npm 安装 Express:
npm install express --save
在跟目录创建一个 index.js
作为入口文件,启动一个 HTTP 服务:
const express = require('express');
const app = express();
// 配置请求响应
app.get('/', (req, res) => {
res.send('Hello Express');
});
// 启动服务
app.listen(80, () => {
console.log('server start http:127.0.0.1');
});
在命令行中输入 node index.js
运行,成功运行后命令行会输出 server start http:127.0.0.1
。
上面启动了一个 HTTP 服务,端口使用的是 80,在浏览器中访问 http://localhost
或 http://127.0.0.1
就可以看到 res.send
输出的 Hello Express
。
路由
路由主要包括客户端请求的路径和方式,你可以根据不同的请求路径或请求方式返回不同的内容。
我上面的 app.get
就是处理 GET 请求,第一个参数是路径,/
就是根路径,直接访问域名或 IP ,默认就是根路径,第二个参数就是处理请求的回调函数。
回调函数可以接收两个参数,req
(Request)和 res
(Response)。Request 就是客户端信息,客户端传过来的数据和 Cookie 之类的就需要通过 Request 获取。Response 就是服务端回复,你需要返回数据的时候,包括 body
数据、header
、状态码之类的都可以通过 Response 来设置。
Express 也可以使用 post
、put
、delete
方法来处理对应的请求,使用方式和 get
是一样的:
const express = require('express');
const app = express();
// 配置请求响应
app.get('/', (req, res) => {
res.send('get');
});
// 处理 post 请求
app.post('/', (req, res) => {
res.send('post');
});
// 处理 put 请求
app.put('/', (req, res) => {
res.send('put');
});
// 处理 delete 请求
app.delete('/', (req, res) => {
res.send('delete');
});
一般 GET 请求用来获取数据,POST 请求用来新增数据,PUT 请求用来修改数据,DELETE 请求用来删除数据。
不过很多 API 为了方便,也不会区分的那么细,大多数都是使用 GET 和 POST 请求。
Express 还提供了一个 all
来处理所有 HTTP 请求,使用方法和 get
和 post
是差不多的。
路由路径匹配
路由路径可以是各种符合 URL 地址标准的字符,结尾也可以是文件名后缀,如果你愿意的话,路径还可以是 .php
结尾:
const express = require('express');
const app = express();
app.get('/about', (req, res) => {
res.send('关于网站');
});
app.get('/archives/1.html', (req, res) => {
res.send('1');
});
app.get('/index.php', (req, res) => {
res.send('这是一个 PHP 网页');
});
路由路径也可以使用正则表达式来匹配处理:
const express = require('express');
const app = express();
// 匹配路径中包含 q 的请求
app.get(/q/, (req, res) => {
res.send('路径中包含q');
});
// 匹配 .db 结尾的路径
app.get(/\.db$/, (req, res) => {
res.send('你想干嘛?');
});
// 匹配 archives/数字/ 的路径
app.get(/\/archives\/\d+\//, (req, res) => {
// 输出路由路径中的数字
res.send(/\d+/.exec(req.path)[0]);
});
也可以使用 路径:id
的方式来匹配数字结尾的请求:
const express = require('express');
const app = express();
// 匹配 archives/数字ID
app.get('/archives/:id', (req, res) => {
// 输出路由路径中的数字
res.send(/\d+/.exec(req.path)[0]);
});
上面的路径匹配访问 地址/archives/12
或 地址/archives/12/
都可以匹配到,结尾有没有 /
都能匹配到。
响应请求
在收到 HTTP 请求后就需要响应请求,否则客户端请求会被挂起。
下面是 res
(Response)用于响应请求的一些方法:
res.send
send
可用于响应各种 HTTP 请求,响应 body 可以是 String 字符串、Buffer、对象:
const express = require('express');
const app = express();
const fs = require('fs');
app.get('/img', (req, res) => {
// 读取文件
const file = fs.readFileSync('logo.png');
// 传入文件 Buffer
res.send(file);
});
app.get('/json', (req, res) => {
res.send({name: 'Mr. Ma', age: 17});
});
app.get('/', (req, res) => {
res.send('My blog www.misterma.com');
});
如果传入的是字符串,header
的 Content-type
就是 text/html
,传入 Buffer,Content-type
就是 application/octet-stream
,传入对象,Content-type
就是 application/json
。
我上面的读取文件为 Buffer 只是简单演示,用的是同步的读取文件方式,一般服务器中不会使用同步的方式读取文件,下载文件也不会读取整个文件。
res.end
end
用于在不返回数据的情况下结束响应:
app.get('/', (req, res) => {
res.end('not found');
res.status(404).end();
});
end
也可以返回 String 字符串,但需要返回数据的话,建议用 send
或 json
。
res.json
json
可以发送 JSON 响应,可以传入对象或数组:
res.json({name: 'Mr. Ma', age: 17});
Express 会自动进行 JSON.stringify
和设置正确的 Content-type
。
res.jsonp
因为同源策略的限制,浏览器端是不能跨域请求的,JSONP 就是解决跨域请求的一种方式。
jsonp 和 json 的使用是差不多的,传入对象,返回转换后的 JSON:
res.jsonp({name: 'Mr. Ma', age: 17});
res.download
download
可以用于响应下载文件请求,第一个参数是文件路劲,第二个参数是下载时使用的文件名,下载时使用的文件名可以和真实的文件名不一样:
const path = require('path');
app.get('/download', (req, res) => {
res.download(path.join(__dirname, 'logo.png'), '图片.png');
});
download
也可以接收第三个参数,第三个参数是发生错误时的回调函数:
app.get('/download', (req, res) => {
res.download(path.join(__dirname, 'logo.png'), '图片.png', error => {
// 返回状态码
res.end(`${error.statusCode} error`);
});
});
在文件不存在或没有访问权限的时候就会触发回调函数。
res.redirect
redirect
可以用于重定向,第一个参数是状态码或 URL,如果第一个参数传入了 Numbar 类型的状态码的话,第二个参数就是 URL:
app.get('/', (req, res) => {
res.redirect(301, 'https://www.misterma.com/');
});
URL 可以是同站的其它路径,也可以是其他网站。
状态码如果省略的话,默认是 302。
res.render
render
可以输出模板渲染,第一个参数是模板的变量,第二个参数是出错的回调函数。
模板引擎可以让你在单独的文件中按照正常的格式来编写 HTML,在模板文件中也可以使用变量或判断语法,在运行的时候,模板引擎会把变量替换为实际的数据并生成 HTML 代码。
Express 的模板引擎需要单独安装,并且有很多模板引擎可以选择。
关于模板引擎的使用,我后面会单独写一篇。
res.sendFile
sendFile
可以返回一个文件,第一个参数是文件路径,第二个参数是可选的出错的回调函数:
const path = require('path');
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public/index.html'));
});
sendFile
会根据文件类型来设置 Content-type
,如果是常见浏览器能打开的文件就可以直接在浏览器中显示,如果是浏览器不能打开的文件就会显示文件下载。
sendFile
也可以返回静态的 HTML 文件,如果你的网页是完全的前后端分离的话,也可以使用 sendFile
来输出 HTML 文件。
res.sendStatus
sendStatus
可以直接返回一个状态码,参数就是一个 Numbar 的状态码:
res.sendStatus(404);
处理静态文件
前端网页会用到很多 CSS、JS、图片文件,这些文件没有必要单独编写路由处理,Express 提供了静态文件处理的中间件,可以直接映射到静态资源目录。
下面把项目目录下的 public
设置为静态文件目录:
const express = require('express');
const app = express();
const path = require('path');
// 注册用于静态文件处理的 static 中间件
app.use(express.static(path.join(__dirname, 'public')));
设置完成后 public
目录里的文件都可以通过 地址/文件名
访问到,比如我的 public
目录里有一个 logo.png
,我在本地服务器通过 http://localhost/logo.png
就可以访问到,不需要加目录名。
静态文件目录也可以设置多个目录:
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'static')));
我上面同时设置了 public
和 static
目录,我要访问这两个目录里的文件也是使用 地址/文件名
,不需要加目录名。
如果你需要让地址路径和你实际的路径不一样的话,Express 的 static 也可以设置虚拟路径,下面还是把 public
设置为静态文件目录,地址路径设置为 static
:
app.use('/static', express.static(path.join(__dirname, 'public')));
现在如果我需要访问 public
目录里的 logo.png
我就需要使用 地址/static/logo.png
,比如 http://localhost/static/logo.png
,不能再使用 地址/文件名
。我设置的 static
只存在于地址路径中,实际映射到的还是 public
目录。
中间件
Express 的中间件就是一组处理 HTTP 请求的函数。中间件可分为 应用层中间件、路由中间件、错误处理中间件、内置中间件、第三方中间件。上面处理静态文件使用的就是内置中间件,处理 HTTP 请求的 get
和 post
之类的也是中间件。
Express 使用 use
注册中间件,每一个应用中间件函数都可以接收 req
(Request)、res
(Response)、next
下一个中间件。
下面是一个简单的应用中间件:
const express = require('express');
const app = express();
app.use((req, res) => {
res.send('Hello');
});
上面只要是服务器收到请求,不管是 GET 还是 POST,无论路径是什么,都会返回 hello
。
中间件除了接收 req
和 res
外,还能接收第三个参数 next
,next
可以把请求传递给下一个中间件:
app.use((req, res, next) => {
if (req.path === '/') {
res.send('这是首页');
}
// 如果路径不是 / 就传递给下一个中间件执行
next();
});
app.use((req, res, next) => {
if (req.path === '/about') {
res.send('关于');
}
next();
});
app.use((req, res) => {
res.send('不知道你访问的是哪个页面');
});
上面的第一个中间件会判断请求路径,如果路径是 /
就返回 这是首页
,否则就传给下一个中间件执行,第二个中间件也是一样的,如果不符合条件就再传给下一个中间件执行。
中间件也可以设置路径,只有指定的路由才会触发中间件函数:
app.use('/about', (req, res) => {
res.send('关于');
});
app.use('/user', (req, res) => {
res.send('用户信息');
});
路由中间件
Express 还提供了一个单独的路由系统 Router
,通过 Express 的 Router
也可以注册中间件。
下面把 API 请求处理放到单独的 api-router.js
文件中,使用 Express 的 Router
来注册中间件:
const router = require('express').Router();
// 用户相关
router.use('/user', (req, res) => {
res.send('用户相关');
});
// 文章相关
router.use('/post', (req, res) => {
res.send('文章相关');
});
module.exports = router;
下面引入 api-router.js
注册使用:
const express = require('express');
const app = express();
const apiRouter = require('./api-router');
app.use('/api', apiRouter);
我的中间件注册到了 /api
,我访问 地址/api/user
就会触发用户相关的中间件,访问 地址/api/post
就会触发文章相关的中间件。
版权声明:本文为原创文章,版权归 Mr. Ma's Blog 所有,转载请联系博主获得授权。
本文地址:https://www.misterma.com/archives/932/
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。