Express.js 教程
最后修改于 2023 年 10 月 18 日
在本文中,我们将展示如何使用 Express 框架在 JavaScript 中创建简单的 Web 应用程序。
Express.js
Express.js 是一个用于 Node.js 的免费开源 Web 应用程序框架。 Express 是一个极简且灵活的 Web 应用程序框架,为 Web 和移动应用程序提供了一套强大的功能。
它是一个快速、非主观且极简的 Web 框架。
Express.js 安装
我们安装 Express 框架。 稍后,我们将安装其他软件包,包括 Lodash、sqlite3 和 Axios。
$ node -v v18.2.0
我们使用 Node 版本 18.2.0。
$ npm init -y $ npm i express
我们使用 npm
工具安装 Express。
URL
统一资源定位符 (URL) 是对 Web 资源的引用,它指定了它在计算机网络上的位置和检索它的机制。 Web 资源是可以通过 Web 获取的任何数据,例如 HTML 文档、PDF 文件、PNG 图像、JSON 数据或纯文本。
通用 URL 的形式如下:
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
方括号 []
之间的部分是可选的。
Express.js 路由
一个 路由 将一个 HTTP 动词(例如 GET、POST、PUT、DELETE)和一个 URL 路径与一个处理程序函数关联起来。 为了创建路由,我们使用 Express 应用程序对象的函数。
app.get('/', (req, res) => { });
在这里,我们将 GET 请求中发送的 /
路径映射到处理程序函数。 该函数接收请求和响应对象作为参数。
为了将路由分组并在模块中分离它们,我们可以使用 Router
中间件。
Express.js 中间件
Express 中间件是框架的核心。 它位于请求-响应周期的中间。 中间件是在管道中在请求对象和响应对象之间调用的一个系列函数。 Express 是一个极简框架。 大部分功能都作为中间件函数提供。
中间件函数用于实现身份验证、CSRF 保护、日志记录或 Cookie 处理等功能。 它们不用于实现应用程序的业务逻辑。
use
函数在指定路径安装指定的中间件函数或函数。
Express.js GET 请求示例
get
函数将 HTTP GET 请求路由到指定路径,并使用指定的回调函数。
const express = require('express'); const app = express(); app.get('/', (req, res) => res.send('Hello there!')); app.listen(3000, () => console.log('Application started on port 3000'));
应用程序处理 GET 请求并将一条简短的消息发送给客户端。
const express = require('express');
我们包含 express
包。
const app = express();
创建 Express 应用程序。
app.get('/', (req, res) => res.send('Hello there!'));
使用 get
函数,我们将 /
路径映射到匿名函数,该函数将一个字符串发送回客户端。
app.listen(3000, () => console.log('Application started on port 3000'));
启动后,应用程序监听端口 3000。
$ node main.js Application started on port 3000
我们在本地主机上启动应用程序。
$ curl localhost:3000 Hello there!
我们使用 curl
命令创建了一个请求。
Express.js HTTP 标头
请求对象还包括从客户端发送的请求标头。 请求标头是包含有关要获取的资源以及请求该资源的客户端的更多信息的 HTTP 标头。
const express = require('express'); const app = express(); app.get('/', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send(`The request IP is: ${req.ip}`); }); app.listen(3000, () => { console.log('Application started on port 3000'); });
该示例输出生成请求的客户端的 IP 地址。
$ curl localhost:3000 The request IP is: ::1
这是一个示例输出; ::1
是 IPv6 中的环回地址。
Express.js 查询参数
查询字符串是 URL 的一部分,用于向资源的请求添加一些条件。 它通常是一系列键/值对。 它跟在路径之后,以 ?
字符开头。
const express = require('express'); const app = express(); app.get('/greet', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); let name = req.query.name; let msg = `Hello ${name}`; res.send(msg); }); app.listen(3000, () => console.log('Application started on port 3000'));
应用程序创建并将消息发送给客户端。 它使用来自 name
查询参数的值。
app.get('/greet', (req, res) => {
我们将 HTTP GET 请求路由到指定路径,并使用指定的回调函数。 回调函数接收两个参数:请求对象和响应对象。 请求对象表示 HTTP 请求,并具有用于请求查询字符串、参数、正文和 HTTP 标头的属性。
res.set({ 'Content-Type': 'text/plain; charset=utf-8' });
我们设置响应内容类型和字符集。 我们的输出将是纯文本。 默认内容类型为 text/html
。
let name = req.query.name;
我们从请求查询属性中获取查询参数。 它是一个对象,其中包含路由中每个查询字符串参数的属性。
let msg = `Hello ${name}`;
构建一条消息。
res.send(msg);
消息被发送到客户端。
$ curl -i localhost:3000/greet?name=Lucia HTTP/1.1 200 OK X-Powered-By: Express Content-Type: text/plain; charset=utf-8 Content-Length: 11 ETag: W/"b-QOTbNGLVpb1ETGDcTt/rfpJV0wI" Date: Tue, 21 Apr 2020 06:58:12 GMT Connection: keep-alive
我们使用 curl
命令行工具创建对应用程序的 GET 请求。 使用 -i
选项,我们还包含响应标头。
Express.js 路径参数
可以通过查询参数或路径参数将值发送到 Web 应用程序。 路径参数在冒号 /:param
之后指定。
req.params
属性是一个对象,其中包含映射到命名路由参数的属性。
const express = require('express'); const app = express(); app.get('/show/:name/:age/', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); let name = req.params.name; let age = req.params.age; let msg = `${name} is ${age} years old`; res.send(msg); }); app.listen(3000, () => console.log('Application started on port 3000'));
应用程序向用户返回一条消息,其中包含发送的两个路径参数。
app.get('/show/:name/:age/', (req, res) => {
路径参数在路由路径中定义; 参数的名称跟在冒号字符后面。
let name = req.params.name; let age = req.params.age;
我们从 req.params
对象中检索路径参数。
$ curl localhost:3000/show/Robert/24/ Robert is 24 years old
Express.js 路径模式匹配
可以在请求路径上使用正则表达式。 这样我们可以限制与请求路径一起传递的值。
const express = require('express'); const app = express(); app.get('/city/:id([0-9]{1,5})', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send(`id: ${req.params.id}`); }); app.listen(3000, () => { console.log('Application started on port 3000'); });
在本例中,我们将 Id 值限制为整数,该整数的位数介于 1 到 5 之间。
$ curl localhost:3000/city/12345 id: 12345
这是 Id 12345 的输出。
Express JSON
JSON 是一种轻量级的数据交换格式。它易于人类阅读,也易于机器解析和生成。Web 应用程序通常会消耗和生成 JSON 数据。
res.json
函数发送 JSON 响应。 参数可以是任何 JSON 类型,包括对象、数组、字符串、布尔值、数字或 null。
$ npm i lodash
我们安装 Lodash 库。
const express = require('express'); const _ = require('lodash'); const app = express(); app.get('/movies', (req, res) => { res.set({ 'Content-Type': 'application/json; charset=utf-8' }); let movies = { 1: 'Toy story', 2: 'The Raid', 3: 'Hero', 4: 'Ip Man', 5: 'Kung Fu Panda' }; let movie = _.sample(movies); res.json({movie}); }); app.listen(3000, () => console.log('Application started on port 3000'));
该示例从 movies
对象中选取随机电影。 电影以 JSON 格式返回。
res.set({ 'Content-Type': 'application/json; charset=utf-8' });
我们设置 JSON 数据的内容类型。
let movies = { 1: 'Toy story', 2: 'The Raid', 3: 'Hero', 4: 'Ip Man', 5: 'Kung Fu Panda' };
我们在 JS 对象中有几部电影。
let movie = _.sample(movies);
使用 Lodash _sample
方法,我们选取一部随机电影。
res.json({movie});
选取的电影以 JSON 格式发送到客户端。
$ curl localhost:3000/movies {"movie":"Toy story"} $ curl localhost:3000/movies {"movie":"Kung Fu Panda"}$ $ curl localhost:3000/movies {"movie":"Kung Fu Panda"}
Express bodyparser
body-parser
是一个 Node 请求正文解析中间件。 它在我们的处理程序之前,在中间件中解析传入的请求正文。 数据可在 req.body
属性下使用。
对于此示例,我们需要安装 Axios 包。
$ npm i axios
Axios 是一个基于 promise 的浏览器和 Node.js 的 HTTP 客户端
const express = require("express"); const app = express(); app.use(express.json()); app.post('/info', (req, res) => { console.log(req.body); res.json(req.body); }); app.listen(3000, () => { console.log('Application started on port 3000'); });
在本例中,我们解析 JSON 正文。
app.use(express.json());
在这里,我们应用 body-parser 中间件来解析请求正文中的 JSON 数据。
res.json(req.body);
我们将解析后的数据以 JSON 格式返回给客户端。
const axios = require('axios'); async function makePostRequest() { params = { first_name: 'John', last_name: 'Doe', email: 'gardener' } let res = await axios.post('https://:3000/info/', params); console.log(`Status code: ${res.status}`); console.log(`Status text: ${res.statusText}`); console.log(`Request method: ${res.request.method}`); console.log(`Path: ${res.request.path}`); console.log(res.data); } makePostRequest();
使用 Axios 库,我们向 Express 应用程序发出 POST 请求。
$ node post-request.js Status code: 200 Status text: OK Request method: POST Path: /info/ { first_name: 'John', last_name: 'Doe', email: 'gardener' }
Express post 表单
HTTP POST 方法将数据发送到服务器。 它通常用于上传文件或提交已完成的 Web 表单。 从表单发送的数据存储在请求的正文中。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Form</title> </head> <body> <form action="message" method="post"> <div> <label>Name:</label> <input type="text" name="name"> </div> <div> <label>Message</label> <input type="text" name="message"> </div> <button type="submit">Send</button> </form> </body> </html>
我们有一个包含两个输入字段的表单:name
和 message
。
const express = require('express'); const path = require('path'); const app = express(); app.use(express.static('public')); app.use(express.urlencoded({ extended: true })); app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); app.post('/message', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); let name = req.body.name; let message = req.body.message; let output = `${name} says: ${message}`; res.send(output); }); app.listen(3000, () => { console.log('Application started on port 3000'); })
我们在应用程序中有两条路由。 /
路径将表单带给用户。 该函数只是发送包含表单的静态 HTML 文件。 /message
路径处理表单并从发布的数据构建一条消息。
app.use(express.urlencoded({ extended: true }));
我们应用 urlencoded
中间件来处理表单。 该中间件解析带有 urlencoded 负载的传入请求。 application/x-www-form-urlencoded
内容类型是默认值。 (extended 选项选择用于解析数据的特定库。)
let name = req.body.name; let message = req.body.message; let output = `${name} says: ${message}`;
我们从请求的正文中获取两个参数并构建一个输出。
res.send(output);
输出被发送到客户端。
Express 发送文件
sendFile
函数将文件传输到给定的路径。 图像显示在浏览器中。 download
函数传输图像; 图像被浏览器作为附件提供。
const express = require('express'); const path = require('path'); const app = express(); app.get('/file', (req, res) => { res.set({ 'Content-Type': 'image/jpeg' }); let file = path.join(__dirname, 'img/book.jpg'); // res.sendFile(file); res.download(file, 'book-image.jpg'); }); app.listen(3000, () => { console.log('Application started on port 3000'); })
该示例将图像发送到客户端。 请注意,由于浏览器正在进行缓存,我们可能看不到这两种方法之间的区别。 在这种情况下,我们可以打开一个私人窗口。
res.set({ 'Content-Type': 'image/jpeg' });
我们设置适当的内容类型。
let file = path.join(__dirname, 'img/book.jpg');
我们指定图像的路径。
Express 静态文件
静态文件是不会更改的文件。 它们包括 CSS 文件、JavaScript 文件和图像; 还包括不包含模板指令的 HTML 文件。
要使用静态文件,我们使用内置的 static
中间件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home page</title> </head> <body> <p> This is home page </p> </body> </html>
这是主页。 这是一个静态 HTML 文件的示例。
const express = require('express'); const path = require('path'); const app = express(); app.use(express.static('public')); app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public/index.html')); }); app.listen(3000, () => { console.log('Application started on port 3000'); });
该示例显示了主页的简单静态 HTML 文件。
app.use(express.static('public'));
我们使用静态中间件; 静态文件存储在 public
目录中。
app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public/index.html')); });
sendFile
函数将文件传输到给定的路径。
Express 网站图标
网站图标,也称为网站图标,是与特定网站或网页关联的小图标。 要在 Express 应用程序中显示网站图标,我们可以使用 express-favicon
中间件。
$ npm i express-favicon
我们安装该包。
$ ls public/images/ favicon.ico
我们在 public/images
目录中有 favicon
。
const express = require('express'); const path = require('path'); const favicon = require('express-favicon'); const app = express(); app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.ico'))); app.get('/', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('Home page'); }); app.listen(3000, () => { console.log('Application started on port 3000'); });
该示例显示了一个网站图标。
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.ico')));
我们使用 use
函数应用中间件函数。 图标位于 public/images
目录中。
Express 自定义 404 错误消息
HTTP 404、404 Not Found、404、Page Not Found 错误消息是在 Web 通信中使用的超文本传输协议 (HTTP) 标准响应代码。 它表明浏览器能够与给定的服务器通信,但服务器找不到所请求的资源。
Express 提供了一个简陋的 404 消息。 在以下示例中,我们创建了自定义消息。
const express = require('express'); const app = express(); app.get('/', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('Home page'); }); app.get('/about', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('About page'); }); app.get('/contact', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('Contact page'); }); app.use((req, res) => { res.statusCode = 404; res.end("404 - page not found"); }); app.listen(3000, () => { console.log('Application started on port 3000'); });
在本例中,我们为 404 未找到错误注册了一个错误处理程序函数。 它显示了 404.html
文件。
app.use((req, res) => { res.statusCode = 404; res.end("404 - page not found"); });
我们设置状态代码并完成响应过程。 错误处理的映射位于所有其他映射之后。
Express Router 中间件
基本路由使用应用程序对象的路由方法执行,例如 get
、post
、put
、head
或 delete
。
可以使用 Router
中间件将路由分组并分离到模块中。
const express = require('express'); const router = express.Router(); router.get('/', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('Home page'); }); router.get('/about', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('About page'); }); router.get('/contact', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('Contact page'); }); module.exports = router;
routes.js
模块中有三条路由。 路由绑定到 Router
中间件,该中间件被公开以供包含。
const express = require('express'); const routes = require('./routes'); const app = express(); app.use(routes); app.listen(3000, () => { console.log('Application started on port 3000'); });
我们包含路由并使用 use
将它们应用于应用程序。
Express - 使用模板引擎
模板引擎或模板处理器是一个旨在将模板与数据模型结合起来以生成文档的库。 模板引擎用于生成大量的电子邮件、源代码预处理或生成动态 HTML 页面。
可以使用 Express 使用许多模板引擎; 例如 Liquid、Nunjucks、Pug 或 EJS。
Liquid 是 Shopify 创建的 JavaScript 模板引擎。 Liquid 文件的扩展名为 .liquid
; 它们是静态数据(例如 HTML)和 Liquid 构造的混合。 在 Liquid.js 教程中了解有关 Liquid 的更多信息。
$ npm i liquidjs
我们安装 Liquid 模板引擎。
在 Liquid 中,我们使用双花括号分隔符 {{ }}
进行输出,使用花括号百分比分隔符 {% %}
进行逻辑处理。
{% if user != null %} Hello {{ user.name }} {% endif %}
此代码是示例 Liquid 语法。
const express = require('express'); const path = require('path'); const { Liquid } = require('liquidjs'); const app = express(); const engine = new Liquid(); app.engine('liquid', engine.express()); app.set('views', path.resolve(__dirname, 'views')); app.set('view engine', 'liquid'); app.get('/today', (req, res) => { let today = new Date(); res.render('show_date', {now: today}); }); app.use((req, res) => { res.statusCode = 404; res.end("404 - page not found"); }); app.listen(3000, () => { console.log('Application started on port 3000'); });
在本例中,我们从路径参数中读取一个值,并将其发送到 show_date.liquid
模板文件进行处理。
app.engine('liquid', engine.express()); app.set('views', path.resolve(__dirname, 'views')); app.set('view engine', 'liquid');
我们设置 Liquid 模板引擎。 模板文件位于 views
目录中。
res.render('show_date', {now: today});
render
函数呈现视图并将呈现的 HTML 字符串发送给客户端。 第一个参数是视图名称(不带扩展名); 第二个参数是 locals
对象,其属性定义视图的局部变量。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Show date</title> </head> <body> <p> Today is {{ now }} </p> </body> </html>
这是 show_date.liquid
模板文件。 模板由静态数据和动态数据组成。
<p> Today is {{ now }} </p>
使用 {{}}
语法,我们输出传递给模板的 now
变量的值。
$ curl localhost:3000/today <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Show date</title> </head> <body> <p> Today is Tue Jun 28 2022 11:15:15 GMT+0200 (Central European Summer Time) </p> </body> </html>
Express SQLite 示例
在以下示例中,我们从 SQLite 数据库发送数据。 SQLite 是一个基于文件的关系数据库引擎。
$ npm install sqlite3
我们使用 sqlite3
包。
app-sqlite.js data cities.sql test.db
这是项目结构。
BEGIN TRANSACTION; DROP TABLE IF EXISTS cities; CREATE TABLE cities(id INTEGER PRIMARY KEY, name TEXT, population INTEGER); INSERT INTO cities(name, population) VALUES('Bratislava', 432000); INSERT INTO cities(name, population) VALUES('Budapest', 1759000); INSERT INTO cities(name, population) VALUES('Prague', 1280000); INSERT INTO cities(name, population) VALUES('Warsaw', 1748000); INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000); INSERT INTO cities(name, population) VALUES('New York', 8550000); INSERT INTO cities(name, population) VALUES('Edinburgh', 464000); INSERT INTO cities(name, population) VALUES('Berlin', 3671000); COMMIT;
我们使用这些数据。
$ cd data $ sqlite3 test.db SQLite version 3.37.2 2022-01-06 13:25:41 Enter ".help" for usage hints. sqlite> .read cities.sql sqlite> select * from cities; 1|Bratislava|432000 2|Budapest|1759000 3|Prague|1280000 4|Warsaw|1748000 5|Los Angeles|3971000 6|New York|8550000 7|Edinburgh|464000 8|Berlin|3671000
我们将数据加载到 test.db
数据库中。
const express = require('express'); const sqlite3 = require('sqlite3').verbose(); const app = express(); const db = new sqlite3.Database('data/test.db'); app.get('/', (req, res) => { res.set({ 'Content-Type': 'text/plain; charset=utf-8' }); res.send('Home page'); }); app.get('/cities', (req, res) => { const sql = 'select * from cities'; const params = []; db.all(sql, params, (err, rows) => { if (err) { res.status(400).json({'error': err.message}); return; } if (!rows) { res.status(204).json({'error': 'No cities found'}); return; } res.json({ 'message':'success', 'data':rows }); }); }); app.get('/city/:id', (req, res) => { const sql = 'select * from cities where id = ?'; const params = [req.params.id]; db.get(sql, params, (err, row) => { if (err) { res.status(400).json({'error':err.message}); return; } if (!row) { res.status(204).json({'error': 'City not found'}); return; } res.json({ 'message':'success', 'data':row }); }); }); const server = app.listen(3000, () => { console.log('Application started on port 3000'); }); process.on('SIGINT', () => { db.close((err) => { console.log('Application terminating'); if (err) { console.error(err.message); } console.log('Closing the database connection.'); }); server.close(); });
应用程序中有三条路由。 一个用于主页,另一个用于所有城市,第三个用于特定城市。 这些城市以 JSON 格式返回。
const sqlite3 = require('sqlite3').verbose();
我们包含 sqlite3
包。 verbose
函数生成调试信息。
const db = new sqlite3.Database('data/test.db');
我们连接到数据库文件。
app.get('/cities', (req, res) => { const sql = 'select * from cities'; const params = []; ...
对于 /cities
路由,我们从数据库中提取所有行,并将它们作为 JSON 数据发送给客户端。 没有参数传递给 SQL 语句。
db.all(sql, params, (err, rows) => {
all
函数使用指定的参数运行 SQL 查询,然后调用带有所有结果行的回调函数。
if (err) { res.status(400).json({'error': err.message}); return; }
如果出现错误,我们发送 400 状态代码并返回。
if (!rows) { res.status(204).json({'error': 'No cities found'}); return; }
如果没有找到数据,我们发送 204 No content 状态代码。
res.json({ 'message':'success', 'data':rows });
我们使用 json
函数发送包含所选行的 JSON 响应。
app.get('/city/:id', (req, res) => { const sql = 'select * from cities where id = ?'; const params = [req.params.id]; ...
在此路由中,我们搜索具有特定 Id 的城市。 params 数组包含来自请求路径参数的 Id。
db.get(sql, params, (err, row) => {
get
函数使用指定的参数运行 SQL 查询,然后调用带有第一个结果行的回调函数。
res.json({ 'message':'success', 'data':row });
所选行以 JSON 格式发送给客户端。
$ curl localhost:3000/cities {"message":"success","data":[{"id":1,"name":"Bratislava","population":432000}, {"id":2,"name":"Budapest","population":1759000}, {"id":3,"name":"Prague","population":1280000}, {"id":4,"name":"Warsaw","population":1748000}, {"id":5,"name":"Los Angeles","population":3971000}, {"id":6,"name":"New York","population":8550000}, {"id":7,"name":"Edinburgh","population":464000}, {"id":8,"name":"Berlin","population":3671000}]}
来源
在本文中,我们介绍了 Express.js Web 框架。