NodeJs 第十九章(框架Express)

302 阅读11分钟

前言

在 Node.js 的广阔生态中,Express 框架无疑是一颗璀璨的明星。它以简洁、灵活的特点,成为众多开发者构建 Web 应用的首选工具。今天,就让我们深入探讨 Node.js 中的 Express 框架,揭开它的神秘面纱。

一、Express 框架概述

Express 是一个基于 Node.js 平台的极简、灵活的 Web 应用开发框架,它提供了一系列强大的功能,帮助开发者快速搭建 Web 服务器和处理各种请求。与原生的 Node.js HTTP 模块相比,Express 简化了许多繁琐的操作,使代码更加简洁易读。

Express 框架的核心概念包括路由、中间件和视图引擎。路由负责处理不同的 URL 请求,并将其映射到相应的处理函数;中间件则可以在请求处理的过程中执行一些额外的操作,如日志记录、身份验证等;视图引擎则用于渲染动态页面,将数据与模板相结合,生成最终的 HTML 页面。

二、Express 框架的安装与基本使用

要使用 Express 框架,首先需要安装它。在 Node.js 项目的根目录下,通过 npm 命令即可轻松安装:

npm install express

安装完成后,就可以在项目中引入 Express 并创建一个简单的 Web 服务器了:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello, World!');
});

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

在上述代码中,我们首先引入了 Express 模块,然后创建了一个 Express 应用实例。接着,我们定义了一个根路由(/),当用户访问根路径时,会返回 "Hello, World!"。最后,我们使用 app.listen() 方法启动服务器,并监听指定的端口(这里是 3000)。

三、路由的使用

路由是 Express 框架的重要组成部分,它允许我们根据不同的 URL 请求来执行不同的操作。Express 支持多种 HTTP 方法(如 GET、POST、PUT、DELETE 等)的路由定义。您还可以使用 app.all() 来处理所有 HTTP 方法,使用 app.use()来处理 指定 middleware 作为回调函数

例如,我们可以定义一个处理 POST 请求的路由:

app.post('/users', (req, res) => {
  // 处理用户注册或登录等操作
  res.send('User created successfully');
});

除了基本的路由定义,Express 还支持参数化路由。通过在路由路径中使用参数,我们可以动态地处理不同的请求。例如:

app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  // 根据 userId 获取用户信息
  res.send(`User with id ${userId}`);
});

在上述代码中,:id 是一个参数,当用户访问 /users/1 时,req.params.id 的值将为 1,我们可以根据这个参数来获取相应的用户信息。

app.put('/user', (req, res) => {
  res.send('Got a PUT request at /user')
})
app.delete('/user', (req, res) => {
  res.send('Got a DELETE request at /user')
})

路由路径

在 Express 中,路由路径用于定义应用程序响应请求的 URL 模式。路由路径可以是字符串、字符串模式或正则表达式。Express 使用 path-to-regexp 来匹配路由路径;请参阅 path-to-regexp 文档,了解定义 route paths 的所有可能性。

字符串模式路径

可以使用通配符 * 来匹配任意字符序列。

const express = require('express');
const app = express();

// 匹配以 /products 开头的所有路径
app.get('/products/*', (req, res) => {
    res.send('This is a product-related page.');
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

这里,/products/* 可以匹配 /products/books/products/clothes 等任何以 /products 开头的路径。

正则表达式路径

正则表达式提供了更强大的匹配能力,可以根据复杂的规则来匹配 URL。

const express = require('express');
const app = express();

// 使用正则表达式匹配以数字结尾的路径
app.get(/\d$/, (req, res) => {
    res.send('The URL ends with a digit.');
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在这个例子中,/\d$/ 是一个正则表达式,用于匹配以数字结尾的 URL。例如,http://localhost:3000/123 会触发该路由。

组合路径

可以将上述几种方式组合使用,以满足更复杂的路由需求。

const express = require('express');
const app = express();

// 组合路径参数和通配符
app.get('/categories/:categoryId/*', (req, res) => {
    const categoryId = req.params.categoryId;
    res.send(`You are in category ${categoryId} and exploring sub-pages.`);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

此代码中的路由可以匹配 /categories/5/books 这样的路径,其中 5categoryIdbooks 是通配符匹配的部分。

通过合理使用不同类型的路由路径,你可以灵活地处理各种请求,构建出功能丰富的 Express 应用程序。

路由参数

在 Express 中,路由参数是一种用于捕获 URL 中动态部分的机制。它允许你定义路由时使用占位符,当客户端发起请求时,这些占位符会被实际的值所填充,你可以通过 req.params 对象来访问这些值。以下是关于 Express 路由参数的详细介绍:

基本使用

以下是一个简单的示例,展示如何定义和使用路由参数:

const express = require('express');
const app = express();

// 定义带有路由参数的路由
app.get('/users/:userId', (req, res) => {
    // 通过 req.params 获取路由参数的值
    const userId = req.params.userId;
    res.send(`You are requesting information about user with ID: ${userId}`);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在上述代码中,/users/:userId 里的 :userId 就是一个路由参数。当客户端访问 http://localhost:3000/users/123 时,req.params.userId 的值会被设置为 123,并将包含该用户 ID 的消息发送给客户端。

多个路由参数

你可以在一个路由路径中定义多个路由参数,每个参数用 / 分隔:

const express = require('express');
const app = express();

// 定义包含多个路由参数的路由
app.get('/posts/:postId/comments/:commentId', (req, res) => {
    const postId = req.params.postId;
    const commentId = req.params.commentId;
    res.send(`You are viewing comment ${commentId} of post ${postId}`);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

当客户端访问 http://localhost:3000/posts/456/comments/789 时,req.params.postId 的值为 456req.params.commentId 的值为 789

路由参数的命名规则

  • 路由参数名必须以冒号 : 开头,后面紧跟参数名称,参数名称只能包含字母、数字和下划线。
  • 路由参数是区分大小写的,例如 :userId:userID 是不同的参数。

可选路由参数

在 Express 中,你可以使用正则表达式来实现可选的路由参数。例如,让 :category 参数可选:

const express = require('express');
const app = express();

// 定义包含可选路由参数的路由
app.get('/products(/:category)?', (req, res) => {
    const category = req.params.category;
    if (category) {
        res.send(`You are viewing products in the ${category} category.`);
    } else {
        res.send('You are viewing all products.');
    }
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在这个例子中,(/:category)? 表示 :category 参数是可选的。客户端可以访问 http://localhost:3000/products 查看所有产品,也可以访问 http://localhost:3000/products/electronics 查看特定类别的产品。

使用路由参数进行数据查询

路由参数常用于从数据库或其他数据源中检索特定的数据。以下是一个简单的模拟示例:

const express = require('express');
const app = express();

// 模拟数据库数据
const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
];

// 根据用户 ID 获取用户信息
app.get('/users/:userId', (req, res) => {
    const userId = parseInt(req.params.userId);
    const user = users.find(u => u.id === userId);
    if (user) {
        res.json(user);
    } else {
        res.status(404).send('User not found');
    }
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在这个示例中,根据客户端提供的用户 ID,从模拟的用户数据数组中查找对应的用户信息并返回给客户端。如果未找到匹配的用户,则返回 404 错误。

通过使用路由参数,你可以构建出更加灵活和动态的 Express 应用程序,根据不同的请求动态处理数据

路由路径

app.route() 可以使用 为路由路径创建可链接的路由处理程序。 由于路径是在单个位置指定的,因此创建模块化路由会很有帮助,减少冗余和拼写错误也很有帮助。有关路由的更多信息,请参阅: Router() 文档app.route()

app.route('/book')
  .get((req, res) => {
    res.send('Get a random book')
  })
  .post((req, res) => {
    res.send('Add a book')
  })
  .put((req, res) => {
    res.send('Update the book')
  })

四、中间件的应用

在 Express 中,中间件是处理请求 - 响应周期的函数,它可以访问请求对象(req)、响应对象(res)以及应用程序请求 - 响应周期中的下一个中间件函数(next)。中间件可以用于执行各种任务,如日志记录、身份验证、解析请求体等。以下是关于开发和使用 Express 中间件的详细介绍:

中间件的基本结构

中间件函数通常具有以下形式:

const middlewareFunction = (req, res, next) => {
    // 中间件逻辑
    // 可以对 req 和 res 进行操作
    next(); // 调用 next() 将控制权传递给下一个中间件或路由处理程序
};
  • req:请求对象,包含了客户端请求的信息。
  • res:响应对象,用于向客户端发送响应。
  • next:一个函数,调用它可以将控制权传递给下一个中间件或路由处理程序。

开发和使用不同类型的中间件

1. 应用级中间件

应用级中间件绑定到 app 实例上,对所有或特定路径的请求起作用。

const express = require('express');
const app = express();

// 应用级中间件,记录所有请求的时间
const requestTime = (req, res, next) => {
    req.requestTime = Date.now();
    next();
};

// 使用应用级中间件
app.use(requestTime);

app.get('/', (req, res) => {
    const responseText = `Request received at: ${req.requestTime}`;
    res.send(responseText);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在上述代码中,requestTime 中间件记录了每个请求的时间,并将其存储在 req 对象中,然后通过 next() 函数将控制权传递给下一个中间件或路由处理程序。

2. 路由级中间件

路由级中间件绑定到 express.Router() 实例上,只对特定路由的请求起作用。

const express = require('express');
const app = express();
const router = express.Router();

// 路由级中间件,检查用户是否已登录
const isAuthenticated = (req, res, next) => {
    const isLoggedIn = true; // 模拟用户已登录
    if (isLoggedIn) {
        next();
    } else {
        res.status(401).send('Unauthorized');
    }
};

// 使用路由级中间件
router.get('/dashboard', isAuthenticated, (req, res) => {
    res.send('Welcome to the dashboard!');
});

app.use('/user', router);

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

这里,isAuthenticated 中间件用于检查用户是否已登录,只有登录的用户才能访问 /user/dashboard 路由。

3. 错误处理中间件

错误处理中间件有四个参数(err, req, res, next),用于捕获和处理应用程序中的错误。

const express = require('express');
const app = express();

// 模拟一个会抛出错误的路由
app.get('/error', (req, res, next) => {
    const error = new Error('Something went wrong');
    next(error);
});

// 错误处理中间件
const errorHandler = (err, req, res, next) => {
    console.error(err);
    res.status(500).send('Internal Server Error');
};

// 使用错误处理中间件
app.use(errorHandler);

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

/error 路由抛出错误时,错误会被传递给 errorHandler 中间件进行处理。

4. 第三方中间件

可以使用第三方中间件来扩展 Express 应用的功能,例如 body-parser 用于解析请求体,morgan 用于日志记录。

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const morgan = require('morgan');

// 使用 morgan 中间件进行日志记录
app.use(morgan('dev'));

// 使用 body-parser 中间件解析 JSON 请求体
app.use(bodyParser.json());

app.post('/data', (req, res) => {
    const data = req.body;
    res.json({ message: 'Data received', data });
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在这个例子中,morgan 中间件用于记录每个请求的详细信息,body-parser 中间件用于解析客户端发送的 JSON 请求体。

中间件的使用顺序

中间件的使用顺序非常重要,它们按照定义的顺序依次执行。例如:

const express = require('express');
const app = express();

const firstMiddleware = (req, res, next) => {
    console.log('First middleware');
    next();
};

const secondMiddleware = (req, res, next) => {
    console.log('Second middleware');
    next();
};

app.use(firstMiddleware);
app.use(secondMiddleware);

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

五、响应方式

方法描述
res.download()提示下载文件。
res.end()结束响应进程。
res.json()发送 JSON 响应。
res.jsonp()发送支持 JSONP 的 JSON 响应。
res.redirect()重定向请求。
res.render()渲染视图样板。
res.send()发送各种类型的响应。
res.sendFile()将文件作为八位字节流发送。
res.sendStatus()设置响应状态代码并将其字符串表示形式作为响应正文发送。

1. res.download()

此方法用于提示客户端下载指定的文件。它会设置适当的响应头,让浏览器将文件视为下载项。

const express = require('express');
const app = express();
const path = require('path');

app.get('/download', (req, res) => {
    const filePath = path.join(__dirname, 'example.pdf');
    res.download(filePath, 'downloaded_file.pdf', (err) => {
        if (err) {
            console.error('Error downloading file:', err);
            res.status(500).send('Error downloading file');
        }
    });
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在上述代码中,当客户端访问 /download 路径时,服务器会提示客户端下载 example.pdf 文件,并重命名为 downloaded_file.pdf

2. res.end()

该方法用于结束响应进程。通常在只需要发送少量数据或不需要发送额外数据时使用。

const express = require('express');
const app = express();

app.get('/end', (req, res) => {
    res.end('Response ended');
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

这里,当客户端访问 /end 路径时,服务器会发送 Response ended 并结束响应。

3. res.json()

此方法用于发送 JSON 响应。它会自动设置响应的 Content-Type 头为 application/json

const express = require('express');
const app = express();

app.get('/json', (req, res) => {
    const data = {
        name: 'John',
        age: 30
    };
    res.json(data);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

当客户端访问 /json 路径时,服务器会返回一个包含用户信息的 JSON 对象。

4. res.jsonp()

res.jsonp() 方法用于发送支持 JSONP 的 JSON 响应。JSONP 是一种跨域数据交互的技术。

const express = require('express');
const app = express();

app.get('/jsonp', (req, res) => {
    const data = {
        message: 'This is a JSONP response'
    };
    res.jsonp(data);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

当客户端通过 JSONP 请求 /jsonp 路径时,服务器会返回一个支持 JSONP 的响应。

5. res.redirect()

该方法用于重定向请求到另一个 URL。可以是相对路径或绝对路径。

const express = require('express');
const app = express();

app.get('/redirect', (req, res) => {
    res.redirect('/new-page');
});

app.get('/new-page', (req, res) => {
    res.send('You have been redirected to this new page');
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

当客户端访问 /redirect 路径时,服务器会将其重定向到 /new-page 路径。

6. res.render()

res.render() 方法用于渲染视图模板。通常需要结合模板引擎(如 EJS、Pug 等)使用。

const express = require('express');
const app = express();
const path = require('path');

// 设置视图引擎为 EJS
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

app.get('/render', (req, res) => {
    const data = {
        title: 'My Page',
        content: 'This is the content of the page'
    };
    res.render('index', data);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

在上述代码中,需要在 views 目录下创建一个 index.ejs 文件,服务器会将 data 对象传递给模板进行渲染。

7. res.send()

res.send() 方法可以发送各种类型的响应,包括字符串、缓冲区、对象等。它会根据发送的数据类型自动设置响应头。

const express = require('express');
const app = express();

app.get('/send-string', (req, res) => {
    res.send('This is a string response');
});

app.get('/send-object', (req, res) => {
    const obj = {
        key: 'value'
    };
    res.send(obj);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

分别访问 /send-string/send-object 路径,服务器会发送不同类型的响应。

8. res.sendFile()

该方法用于将文件作为八位字节流发送给客户端。

const express = require('express');
const app = express();
const path = require('path');

app.get('/send-file', (req, res) => {
    const filePath = path.join(__dirname, 'example.txt');
    res.sendFile(filePath);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

当客户端访问 /send-file 路径时,服务器会发送 example.txt 文件的内容。

9. res.sendStatus()

res.sendStatus() 方法用于设置响应状态代码,并将其字符串表示形式作为响应正文发送。

const express = require('express');
const app = express();

app.get('/send-status', (req, res) => {
    res.sendStatus(404);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

当客户端访问 /send-status 路径时,服务器会返回 404 状态码,并将 “Not Found” 作为响应正文。

这些方法在 Express 应用中非常重要,它们能帮助你根据不同的需求向客户端发送合适的响应。

六、视图引擎的选择与使用

为了渲染动态页面,Express 支持多种视图引擎,如 EJS、Pug、Handlebars 等。我们可以根据自己的需求选择合适的视图引擎。以 EJS 为例,我们首先需要安装 ejs 模块:

npm install ejs

然后,在 Express 应用中设置 EJS 为视图引擎:

app.set('view engine', 'ejs');

接下来,我们可以创建一个 EJS 模板文件(如 index.ejs),并在路由处理函数中渲染该模板:

app.get('/', (req, res) => {
  const data = {
    title: 'My Express App',
    message: 'Welcome to my app'
  };
  res.render('index', data);
});

index.ejs 模板文件中,我们可以使用 EJS 语法来插入动态数据:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title><%= title %></title>
</head>

<body>
  <h1><%= message %></h1>
</body>

</html>

通过以上步骤,我们就可以使用 EJS 视图引擎来渲染动态页面了。

七、利用 Express 托管静态文件

为了提供诸如图像、CSS 文件和 JavaScript 文件之类的静态文件,使用 Express 中的 express.static 内置中间件函数。

express.static(root, [options])

例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:

app.use(express.static('public'))

现在,你就可以访问 public 目录中的所有文件了:

http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html

Express 在静态目录查找文件,因此,存放静态文件的目录名不会出现在 URL 中。

如果要使用多个静态资源目录,请多次调用 express.static 中间件函数:

app.use(express.static('public'))
app.use(express.static('files'))

访问静态资源文件时,express.static 中间件函数会根据目录的添加顺序查找所需的文件。

app.use('/static', express.static('public'))

现在,你就可以通过带有 /static 前缀地址来访问 public 目录中的文件了。

http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html

八、Express 框架的优势与应用场景

Express 框架的优势在于其简洁性和灵活性。它提供了丰富的功能和插件,使得开发者可以根据项目的需求快速搭建 Web 应用。同时,Express 框架的性能也非常出色,能够处理大量的并发请求。

Express 框架适用于各种类型的 Web 应用开发,包括小型的个人博客、企业网站、电子商务平台等。无论是前端开发还是后端开发,Express 都能为开发者提供高效的解决方案。

Express 框架作为 Node.js 生态中的重要组成部分,为开发者提供了一个强大、灵活的 Web 应用开发平台。通过学习和掌握 Express 框架的核心概念和使用方法,我们可以更加高效地构建出功能丰富、性能卓越的 Web 应用。在未来的开发工作中,相信 Express 框架将继续发挥重要作用,助力我们实现更多的创新和突破。

结尾

本文粗略的讲解了一下Express 的简单使用, 后面会使用express 搭建一个完整的服务作为练习