Node.js中使用Express框架构建Web应用
一、Express 框架简介
Express 是基于 Node.js 平台,快速、开放、极简的 web 应用开发框架。它提供了一系列强大的特性,用于创建各种类型的 web 应用,无论是简单的静态页面服务器,还是复杂的单页应用(SPA)或 RESTful API 服务器。
Express 之所以如此受欢迎,原因众多。其一,它的学习曲线较为平缓,对于熟悉 JavaScript 和基本 web 开发概念的开发者来说,上手容易。其二,它高度灵活,开发者可以根据自己的需求,选择合适的中间件来扩展应用功能。其三,Express 与 Node.js 的生态系统紧密结合,能充分利用 Node.js 的异步 I/O 特性,从而实现高性能的 web 应用。
二、环境搭建
在开始使用 Express 构建 web 应用之前,首先需要确保 Node.js 已经安装在本地环境中。可以通过在终端输入 node -v
命令来检查 Node.js 的版本。如果未安装,可以从 Node.js 官方网站(https://nodejs.org/)下载并安装。
接下来,创建一个新的项目目录。例如,在终端中执行以下命令:
mkdir my-express-app
cd my-express-app
然后,初始化一个新的 package.json
文件,该文件用于管理项目的依赖和配置信息。执行以下命令:
npm init -y
-y
参数表示使用默认配置快速初始化,这样会在项目目录下生成一个 package.json
文件。
安装 Express 依赖,在项目目录下执行:
npm install express
这会将 Express 及其依赖下载到项目的 node_modules
目录中,并在 package.json
文件的 dependencies
字段中记录 Express 的版本信息。
三、第一个 Express 应用
创建一个新的 JavaScript 文件,例如 app.js
。在这个文件中,引入 Express 模块并创建一个 Express 应用实例:
const express = require('express');
const app = express();
这里,通过 require('express')
引入 Express 模块,然后调用 express()
函数创建一个 Express 应用实例 app
。
接下来,定义一个简单的路由。路由是指客户端请求与服务器响应之间的映射关系。例如,定义一个处理根路径('/'
)请求的路由:
app.get('/', (req, res) => {
res.send('Hello, World!');
});
上述代码中,app.get
方法用于定义一个处理 GET 请求的路由。第一个参数 '/'
表示该路由匹配根路径,第二个参数是一个回调函数。当客户端向根路径发送 GET 请求时,会执行这个回调函数。req
对象包含了关于请求的信息,res
对象用于向客户端发送响应。res.send
方法用于发送一个简单的文本响应。
最后,启动服务器,监听指定的端口。例如,监听 3000 端口:
const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
app.listen
方法用于启动服务器并开始监听指定的端口。回调函数会在服务器成功启动后执行,输出一条日志信息表示服务器已在指定端口运行。
完整的 app.js
文件代码如下:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, World!');
});
const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
在终端中运行 node app.js
启动服务器,然后在浏览器中访问 http://localhost:3000
,就可以看到 Hello, World!
的响应。
四、路由详解
- 基本路由方法
Express 提供了多种路由方法,对应不同的 HTTP 请求方法,如
get
、post
、put
、delete
等。这些方法用于定义不同类型请求的处理逻辑。
get
方法:用于处理 GET 请求,通常用于获取资源。例如,获取一篇文章的详情:
app.get('/articles/:id', (req, res) => {
const articleId = req.params.id;
// 这里可以根据 articleId 从数据库或其他数据源获取文章详情
res.send(`Article with id ${articleId}`);
});
在上述代码中,/:id
是一个路由参数。req.params
对象包含了所有的路由参数,通过 req.params.id
可以获取具体的 id
值。
post
方法:用于处理 POST 请求,通常用于创建新资源。例如,提交一篇新文章:
app.post('/articles', (req, res) => {
// 这里可以从 req.body 中获取提交的文章数据
// 然后将文章保存到数据库等操作
res.send('Article created successfully');
});
- 路由路径匹配 路由路径可以是字符串、字符串模式或正则表达式。
-
字符串匹配:如前面的例子
app.get('/', (req, res) => { ... })
,精确匹配根路径。 -
字符串模式匹配:可以使用通配符
?
、+
、*
等。例如,app.get('/user/:name?', (req, res) => { ... })
中的:name?
表示name
是可选参数。当请求/user
或/user/john
时,该路由都会匹配。 -
正则表达式匹配:使用正则表达式可以实现更灵活的匹配。例如,
app.get(/\/ab+c/, (req, res) => { ... })
会匹配/abc
、/abbc
等路径。
- 路由参数 路由参数在定义动态路由时非常有用。除了前面提到的获取单个参数,还可以有多个参数。例如:
app.get('/products/:category/:productId', (req, res) => {
const category = req.params.category;
const productId = req.params.productId;
res.send(`Product ${productId} in category ${category}`);
});
- 嵌套路由 可以将路由组织成嵌套结构,使代码更具逻辑性。例如,有一个管理用户的模块,其路由可以这样组织:
const userRouter = express.Router();
userRouter.get('/list', (req, res) => {
// 获取用户列表逻辑
res.send('User list');
});
userRouter.get('/:userId', (req, res) => {
const userId = req.params.userId;
// 获取单个用户详情逻辑
res.send(`User with id ${userId}`);
});
app.use('/users', userRouter);
这里首先创建了一个 express.Router
实例 userRouter
,然后在这个实例上定义了两个路由。最后通过 app.use('/users', userRouter)
将 userRouter
挂载到 /users
路径下。这样,访问 /users/list
和 /users/123
等路径就会触发相应的路由处理逻辑。
五、中间件
-
什么是中间件 中间件是 Express 应用中非常重要的概念。它本质上是一个函数,在请求到达最终路由处理函数之前,会依次经过一系列中间件函数。这些中间件函数可以对请求进行各种处理,如日志记录、请求体解析、错误处理等。
-
内置中间件 Express 提供了一些内置中间件,其中最常用的是
express.json()
和express.urlencoded()
。
express.json()
:用于解析 JSON 格式的请求体。在处理包含 JSON 数据的 POST 请求时非常有用。例如:
app.use(express.json());
app.post('/data', (req, res) => {
const data = req.body;
res.send(`Received data: ${JSON.stringify(data)}`);
});
这里通过 app.use(express.json())
使用了 express.json()
中间件,之后在处理 /data
路由的 POST 请求时,就可以从 req.body
中获取解析后的 JSON 数据。
express.urlencoded()
:用于解析 URL 编码格式的请求体。通常用于处理 HTML 表单提交的数据。例如:
app.use(express.urlencoded({ extended: true }));
app.post('/form', (req, res) => {
const name = req.body.name;
const age = req.body.age;
res.send(`Name: ${name}, Age: ${age}`);
});
express.urlencoded()
的 extended
参数设置为 true
时,支持更复杂的 URL 编码格式数据解析。
- 自定义中间件
开发者也可以编写自己的中间件。自定义中间件函数接受
req
、res
和next
三个参数。next
函数用于将控制权传递给下一个中间件或路由处理函数。如果不调用next
,请求处理将终止。
例如,一个简单的日志记录中间件:
const logger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
};
app.use(logger);
上述代码定义了一个 logger
中间件,它会在每次请求到达时记录请求的时间、方法和 URL。然后通过 app.use(logger)
将这个中间件应用到整个应用中。
- 中间件应用顺序
中间件的应用顺序非常关键。Express 会按照
app.use
或路由定义的顺序依次执行中间件。例如,如果有一个错误处理中间件,应该放在所有其他中间件和路由定义之后:
// 日志记录中间件
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
// 处理路由
app.get('/', (req, res) => {
res.send('Home page');
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});
这样,当路由处理过程中发生错误时,会被错误处理中间件捕获并处理。
六、处理静态文件
在 web 应用中,通常需要提供静态文件,如 HTML、CSS、JavaScript 和图片等。Express 可以很方便地处理静态文件。
使用 express.static
中间件来指定静态文件的目录。例如,假设项目目录下有一个 public
目录,存放所有的静态文件:
app.use(express.static('public'));
这样,当客户端请求 /styles.css
时,如果 public
目录下有 styles.css
文件,Express 会直接将该文件返回给客户端。
如果希望通过一个特定的前缀来访问静态文件,可以这样做:
app.use('/static', express.static('public'));
此时,客户端需要通过 /static/styles.css
来访问 public
目录下的 styles.css
文件。
七、模板引擎
模板引擎可以让开发者更方便地生成动态 HTML 页面。Express 支持多种模板引擎,如 EJS、Pug(原名 Jade)等。
- 使用 EJS 模板引擎 首先安装 EJS:
npm install ejs
然后在 Express 应用中配置 EJS:
app.set('view engine', 'ejs');
假设在项目目录下创建一个 views
目录,用于存放 EJS 模板文件。创建一个 index.ejs
文件:
<!DOCTYPE html>
<html>
<head>
<title>My EJS Page</title>
</head>
<body>
<h1>Welcome, <%= name %></h1>
</body>
</html>
在路由中渲染这个模板:
app.get('/', (req, res) => {
const data = { name: 'John' };
res.render('index', data);
});
这里通过 res.render
方法渲染 index.ejs
模板,并传递了一个包含 name
字段的数据对象。在模板中,通过 <%= name %>
可以将 name
的值插入到 HTML 中。
- 使用 Pug 模板引擎 安装 Pug:
npm install pug
配置 Express 使用 Pug:
app.set('view engine', 'pug');
创建一个 views
目录,并在其中创建一个 index.pug
文件:
doctype html
html
head
title My Pug Page
body
h1 Welcome, #{name}
在路由中渲染 Pug 模板:
app.get('/', (req, res) => {
const data = { name: 'Jane' };
res.render('index', data);
});
Pug 使用 #{name}
来插入变量值。
八、错误处理
在 Express 应用开发过程中,难免会遇到各种错误。合理的错误处理机制可以提高应用的稳定性和用户体验。
- 同步错误处理
在路由处理函数或中间件中,如果发生同步错误,可以通过调用
next(err)
将错误传递给错误处理中间件。例如:
app.get('/error', (req, res, next) => {
try {
// 可能会抛出错误的代码
const result = 1 / 0;
res.send('Result: ' + result);
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});
这里在 /error
路由处理函数中捕获到除零错误后,通过 next(err)
将错误传递给全局的错误处理中间件。
- 异步错误处理
对于异步操作,如读取文件、数据库查询等,如果发生错误,同样需要处理。例如,使用
fs.readFile
异步读取文件:
const fs = require('fs');
const util = require('util');
const readFileAsync = util.promisify(fs.readFile);
app.get('/file', async (req, res, next) => {
try {
const data = await readFileAsync('nonexistentfile.txt', 'utf8');
res.send(data);
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Error reading file');
});
这里使用 async/await
处理异步操作,捕获到错误后通过 next(err)
传递给错误处理中间件。
九、构建 RESTful API
RESTful API 是目前非常流行的 web 服务架构风格。使用 Express 可以轻松构建 RESTful API。
- 资源路由设计
假设要构建一个管理书籍的 API,资源是书籍。常见的操作有获取所有书籍(
GET /books
)、获取单个书籍(GET /books/:id
)、创建新书籍(POST /books
)、更新书籍(PUT /books/:id
)和删除书籍(DELETE /books/:id
)。
const express = require('express');
const app = express();
app.use(express.json());
// 模拟数据库,这里使用一个数组
let books = [];
// 获取所有书籍
app.get('/books', (req, res) => {
res.json(books);
});
// 获取单个书籍
app.get('/books/:id', (req, res) => {
const bookId = parseInt(req.params.id);
const book = books.find(b => b.id === bookId);
if (book) {
res.json(book);
} else {
res.status(404).send('Book not found');
}
});
// 创建新书籍
app.post('/books', (req, res) => {
const newBook = req.body;
newBook.id = books.length + 1;
books.push(newBook);
res.status(201).json(newBook);
});
// 更新书籍
app.put('/books/:id', (req, res) => {
const bookId = parseInt(req.params.id);
const updatedBook = req.body;
const index = books.findIndex(b => b.id === bookId);
if (index!== -1) {
books[index] = {...books[index],...updatedBook };
res.json(books[index]);
} else {
res.status(404).send('Book not found');
}
});
// 删除书籍
app.delete('/books/:id', (req, res) => {
const bookId = parseInt(req.params.id);
const index = books.findIndex(b => b.id === bookId);
if (index!== -1) {
const deletedBook = books[index];
books.splice(index, 1);
res.json(deletedBook);
} else {
res.status(404).send('Book not found');
}
});
const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
- API 版本控制
为了保证 API 的兼容性和可扩展性,版本控制很重要。一种常见的方式是在 URL 中包含版本号,如
/v1/books
。可以通过中间件或路由前缀来实现版本控制。
例如,使用路由前缀:
const v1Router = express.Router();
v1Router.get('/books', (req, res) => {
// v1 版本获取书籍逻辑
res.send('V1 books');
});
app.use('/v1', v1Router);
这样,通过 /v1/books
访问的是 v1 版本的书籍 API。
十、部署 Express 应用
-
本地部署 在开发过程中,可以直接在本地通过
node app.js
命令启动 Express 应用。但是这种方式在生产环境中并不适用,因为它不具备进程管理、日志记录等功能。 -
使用 PM2 部署 PM2 是一个流行的 Node.js 进程管理器。首先安装 PM2:
npm install -g pm2
然后在项目目录下使用 PM2 启动应用:
pm2 start app.js --name my-express-app
--name
参数用于给应用实例命名,方便管理。PM2 会自动重启崩溃的进程,并且提供日志记录等功能。可以通过 pm2 logs my-express-app
查看应用日志。
- 部署到云平台 许多云平台都支持部署 Node.js 应用,如 Heroku、AWS Elastic Beanstalk、Google Cloud Platform 等。以 Heroku 为例,首先需要安装 Heroku CLI,然后在项目目录下执行以下步骤:
- 初始化 Git 仓库:
git init
- 添加所有文件:
git add.
- 提交更改:
git commit -m "Initial commit"
- 登录 Heroku:
heroku login
- 创建 Heroku 应用:
heroku create
- 推送代码到 Heroku:
git push heroku master
Heroku 会自动检测项目是 Node.js 应用,并安装依赖、启动应用。
通过以上详细的介绍,相信开发者已经能够熟练使用 Express 框架构建各种类型的 web 应用,无论是简单的网站还是复杂的 RESTful API 服务。在实际开发中,还需要不断探索和优化,结合具体业务需求,充分发挥 Express 的强大功能。