MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js中使用Express框架构建Web应用

2024-09-153.3k 阅读

一、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! 的响应。

四、路由详解

  1. 基本路由方法 Express 提供了多种路由方法,对应不同的 HTTP 请求方法,如 getpostputdelete 等。这些方法用于定义不同类型请求的处理逻辑。
  • 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');
});
  1. 路由路径匹配 路由路径可以是字符串、字符串模式或正则表达式。
  • 字符串匹配:如前面的例子 app.get('/', (req, res) => { ... }),精确匹配根路径。

  • 字符串模式匹配:可以使用通配符 ?+* 等。例如,app.get('/user/:name?', (req, res) => { ... }) 中的 :name? 表示 name 是可选参数。当请求 /user/user/john 时,该路由都会匹配。

  • 正则表达式匹配:使用正则表达式可以实现更灵活的匹配。例如,app.get(/\/ab+c/, (req, res) => { ... }) 会匹配 /abc/abbc 等路径。

  1. 路由参数 路由参数在定义动态路由时非常有用。除了前面提到的获取单个参数,还可以有多个参数。例如:
app.get('/products/:category/:productId', (req, res) => {
    const category = req.params.category;
    const productId = req.params.productId;
    res.send(`Product ${productId} in category ${category}`);
});
  1. 嵌套路由 可以将路由组织成嵌套结构,使代码更具逻辑性。例如,有一个管理用户的模块,其路由可以这样组织:
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 等路径就会触发相应的路由处理逻辑。

五、中间件

  1. 什么是中间件 中间件是 Express 应用中非常重要的概念。它本质上是一个函数,在请求到达最终路由处理函数之前,会依次经过一系列中间件函数。这些中间件函数可以对请求进行各种处理,如日志记录、请求体解析、错误处理等。

  2. 内置中间件 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 编码格式数据解析。

  1. 自定义中间件 开发者也可以编写自己的中间件。自定义中间件函数接受 reqresnext 三个参数。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) 将这个中间件应用到整个应用中。

  1. 中间件应用顺序 中间件的应用顺序非常关键。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)等。

  1. 使用 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 中。

  1. 使用 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 应用开发过程中,难免会遇到各种错误。合理的错误处理机制可以提高应用的稳定性和用户体验。

  1. 同步错误处理 在路由处理函数或中间件中,如果发生同步错误,可以通过调用 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) 将错误传递给全局的错误处理中间件。

  1. 异步错误处理 对于异步操作,如读取文件、数据库查询等,如果发生错误,同样需要处理。例如,使用 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。

  1. 资源路由设计 假设要构建一个管理书籍的 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}`);
});
  1. 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 应用

  1. 本地部署 在开发过程中,可以直接在本地通过 node app.js 命令启动 Express 应用。但是这种方式在生产环境中并不适用,因为它不具备进程管理、日志记录等功能。

  2. 使用 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 查看应用日志。

  1. 部署到云平台 许多云平台都支持部署 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 的强大功能。