Easton Notes
知识八股Express

路由匹配:从 URL 到处理函数

如何在 Express 中创建一个路由?

回答重点

在 Express 中创建一个路由非常简单。首先,确保你已经安装了 Express,并且已经创建了一个基本的 Express 应用程序。接下来,你要创建一个路由模块,使用 express.Router 类来定义你的路由。

具体步骤如下:

1)引入 Express 并创建一个 Express 应用:

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

2)创建一个新的路由模块(如 routes.js)并使用 express.Router 来定义路由:

const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
  res.send('Hello, World!');
}
);
router.post('/data', (req, res) => {
  res.send('Data received!');
}
);
module.exports = router;

3)在主文件中(如 app.js),将刚才创建的路由模块引入并使用它:

const routes = require('./routes');
app.use('/', routes);

4)使你的应用程序开始监听端口:

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

总的来说,这样你就完成了一个基本的 Express 路由创建过程。

扩展知识

为了更全面地理解和使用 Express 路由系统,咱们可以再深入一些:

1)路由的组织和模块化:在一个大型的应用中,组织路由非常重要。你可以将不同功能模块的路由分开放在不同的文件中。比如可以通过 routes/user.js文件专门处理用户相关的路由。以后可以通过类似 app.use('/users', userRoutes); 的方式加载这些路由模块。

2)中间件的使用:Express 中间件是一个功能强大的工具,可以在请求处理的不同阶段执行某些操作。例如身份验证、日志记录、错误处理等可以通过中间件来实现。每个路由都可以单独使用中间件,也可以在整个应用程序范围内使用。 例如:

// 只对特定路由使用中间件 router.get('/private', verifyToken, (req, res) => {
res.send('Private route accessed!');
}
);
// 全局使用中间件 app.use(loggingMiddleware);

3)使用路由参数:对于一些动态的 URL 你可以使用路由参数来捕获参数值。在 Express 路由中你可以使用冒号 : 来定义一个参数:

router.get('/user/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`User ID: ${userId}`);
}
);

4)更复杂的请求处理:你依然可以在同一个路由中使用多个中间件函数,例如:

router.get('/example', middleware1, middleware2, (req, res) => {
  res.send('Example route with multiple middlewares');
}
);

在 Express 中如何使用参数匹配路由?

回答重点

在 Express 中使用参数匹配路由非常简单。通过定义带有冒号(:)的路由参数,我们可以将 URL 中的特定部分捕获为参数。以下是一个简单的示例:

const express = require('express');
const app = express();
// 使用带有参数的路由 app.get('/user/:userId', (req, res) => {
const userId = req.params.userId;
// 获取路由参数 res.send(`User ID is: ${userId}`);
}
);
const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
}
);

在上述代码中,当你访问 /user/123,服务器会响应 User ID is: 123。这里的 :userId 就是路由参数,req.params.userId 就是获取到的参数值。

扩展知识

1)多个参数匹配 你可以在路由中使用多个参数,这些参数在 req.params 对象中都可以访问,例如:

app.get('/user/:userId/book/:bookId', (req, res) => {
  const userId = req.params.userId;
  const bookId = req.params.bookId;
  res.send(`User ID is: ${userId} and Book ID is: ${bookId}`);
}
);

访问 /user/123/book/456 会得到响应 User ID is: 123 and Book ID is: 456

2)可选参数 在一些情况下,我们可能希望某个参数是可选的,这可以通过在参数名后加上 ? 来实现。例如:

app.get('/user/:userId/book/:bookId?', (req, res) => {
  if (req.params.bookId) {
    res.send(`User ID is: ${req.params.userId} and Book ID is: ${req.params.bookId}`);
  } else {
  res.send(`User ID is: ${req.params.userId}`);
}
}
);

3)正则表达式匹配 Express 路由还允许使用正则表达式进行更复杂的 URL 匹配,例如:

app.get('/user/:userId(\\d+)', (req, res) => {
  // 这里只会捕获数字的 userId res.send(`User ID is: ${req.params.userId}`);
}
);

在上面这个示例中,只会匹配 /user/123 这样的路径,而不会匹配 /user/abc

4)中间件与参数匹配 在实际开发中,我们可能需要在参数匹配的路由上附加一些中间件,例如检查参数是否合法或处理某些预处理逻辑。可以这样做:

function validateUserId(req, res, next) {
  if (isNaN(req.params.userId)) {
    return res.status(400).send("Invalid user ID");
  } next();
} app.get('/user/:userId', validateUserId, (req, res) => {
res.send(`User ID is: ${req.params.userId}`);
}
);

这样,当用户访问 /user/xyz 时,服务器会响应 Invalid user ID

Express 内部是如何处理路由匹配的,底层机制是什么?

回答重点

Express 通过中间件堆栈机制实现了路由匹配。每一个路由实际上就是一个中间件,当一个请求到达时,Express 内部会依次遍历这个中间件堆栈,直到找到匹配的路由。它的实现主要通过 app.use()app.METHOD() 这两个函数来完成, app.METHOD() 用以处理不同 HTTP 方法的路由,而 app.use() 则更多用于挂载中间件。

当收到一个 HTTP 请求时,Express 会从堆栈的第一个中间件开始,逐个检查是否匹配当前的请求路径和方法。如果匹配,则执行相关的回调函数,然后进入下一个中间件。如果不匹配,则直接跳到下一个中间件继续检查。这种机制有效地实现了路由的链式匹配和处理。

扩展知识

  1. Express 中间件机制: 中间件是 Express 的核心概念。中间件函数的主要作用有三个:

    • 执行任何代码。
    • 修改请求对象和响应对象。
    • 结束请求-响应周期。
  2. 中间件类型

    • 应用级中间件:绑定到 app 实例上的中间件。
    • 路由级中间件:特定于某个路由的中间件,通过 router 挂载。
    • 内置中间件:Express 提供的一些内置中间件,比如 express.static,用于提供静态文件。
    • 第三方中间件:通过 npm 安装并使用的一些中间件库。
  3. 中间件执行顺序

    • Express 中间件按照定义的先后顺序执行。
    • 可以通过 next() 明示地传递控制权到下一个中间件。
    • 顺序至关重要,因为先定义的中间件会优先执行。
  4. 路由参数和路径

    • Express 支持路径参数,如 /users/:id,可以通过 req.params 访问。
    • 支持正则表达式来匹配复杂路径。
    • 可以使用 app.route() 方法为同一路径设置多种 HTTP 方法处理。

用一个简单代码示例来说明:

const express = require('express');
const app = express();
// 应用级中间件 app.use((req, res, next) => {
console.log('Request Type:', req.method);
next();
}
);
// 路由级中间件 app.get('/user/:id', (req, res, next) => {
console.log('ID:', req.params.id);
next();
}
, (req, res) => {
  res.send('User Info');
}
);
// 处理未匹配的路由(错误处理) app.use((req, res, next) => {
res.status(404).send("Sorry can't find that!");
}
);
app.listen(3000, () => {
  console.log('Server is running on port 3000');
}
);
  1. 底层机制
    • 通过 Layer 和 Route 两个构造函数实现。
    • Layer 代表中间件堆栈中的每一层,包含路径和处理函数。
    • Route 代表一条具体的路由,它包含一组处理特定 HTTP 方法的 Layer

什么是 Express 中的路由方法链式调用

回答重点

在 Express 中,路由方法链式调用是指在编写路由处理函数时,可以通过链式调用的方式,将多个请求处理函数串联在一起。这种方式可以让代码更加简洁和易读,避免了重复的代码,提高了代码的可维护性。

举个简单的例子,当你想要针对同一个路由路径处理不同的 HTTP 方法时,可以通过链式调用将这些方法连接起来:

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

在这个代码示例中,app.route('/user') 后面的 .get().post().put() 方法就通过链式调用的方式串联在了一起。

扩展知识

1)链式调用的好处: 链式调用的最大好处是让代码变得更加简洁。由于可以将多次调用串联在一起,可以减少重复的代码和语法的冗余。另外,在处理逻辑较为复杂的情况下,链式调用还能增加代码的可读性,使开发人员更容易理解代码的逻辑结构。

2)如何实现链式调用: 在 JavaScript 中,实现链式调用的关键在于,每个方法调用之后,返回的是同一个对象。这样下一个方法调用时,还能继续进行操作。在 Express 中,基于这种设计模式,路由方法的每次调用都会返回路由对象本身,使得可以进行链式调用。

3)动态创建路由: 使用 Express 时,除了直接在 app 对象上定义路由,还可以使用 express.Router() 创建一个路由模块。这样做的好处是方便管理更复杂的路由结构,同时支持链式调用:

const router = express.Router();
router.route('/user') .get((req, res) => {
  res.send('Get a user through router');
}
) .post((req, res) => {
  res.send('Add a user through router');
}
);
app.use('/api', router);

在这个例子中,我们创建一个路由 router 对象,然后将其挂载到主应用程序的 /api 路径下。这样可以在大型应用程序中辅助分离路由逻辑。

4)中间件与链式调用: 链式调用不仅可以用来定义不同的 HTTP 方法处理函数,还可以用来引入中间件。中间件是在 Express 应用程序中执行某些操作的函数,非常强大且灵活:

app.route('/user') .all((req, res, next) => {
  console.log('Logging request info');
  next();
}
) .get((req, res) => {
  res.send('Get a random user');
}
) .post((req, res) => {
  res.send('Add a user');
}
);

在这个例子中,.all() 方法作为一个中间件处理函数,被应用于所有后续的处理函数。

在 Express 中如何使用路由器(Router)模块化路由

回答重点

在 Express 中,为了使路由更加模块化和组织有序,可以使用 Router 对象来创建独立的路由模块。通过使用 Router 模块化路由,可以大大简化服务器端代码的管理和维护。下面是一个具体的示例,展示了如何在 Express 应用中实现模块化路由。

1)先安装 Express:

npm install express

2)创建一个 server.js 文件,这是你的应用入口:

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

// 导入路由模块
const userRouter = require('./routes/users');

// 使用路由模块
app.use('/users', userRouter);

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

3)创建一个 routes 目录,然后在 routes 目录下创建一个 users.js 文件,该文件将包含用户相关的路由:

const express = require('express');
const router = express.Router();
// 定义用户首页路由 router.get('/', (req, res) => {
res.send('用户首页');
}
);
// 定义用户详情路由 router.get('/:id', (req, res) => {
res.send(`用户ID: ${req.params.id}`);
}
);
module.exports = router;

扩展知识

使用 Router 模块化路由只是 Express 的一个基本用法,为了进一步完善和扩展你的 Express 应用,可以参考以下几点:

1)中间件:可以在路由器级别使用中间件用于处理特定路由组的前置逻辑,例如身份验证。

router.use((req, res, next) => {
  console.log('用户请求时间:', Date.now());
next();
}
);

2)错误处理:定义一个错误处理函数来处理路由中的错误,提高代码的健壮性。

router.get('/error', (req, res, next) => {
  try {
    // 模拟错误
    throw new Error('模拟错误');
  }
  catch (err) {
    next(err);
  }
}
);
app.use((err, req, res, next) => {
  res.status(500).send('服务器错误: ' + err.message);
}
);

3)参数验证:使用库如 Joivalidator 来验证路由参数,确保数据有效性。

const Joi = require('joi');
router.post('/create', (req, res) => {
  const schema = Joi.object({
    username: Joi.string().min(3).required(), email: Joi.string().email().required()
  }
  );
  const {
    error
  } = schema.validate(req.body);
  if (error) {
    return res.status(400).send(error.details[0].message);
  } res.send('用户创建成功');
}
);

如何在 Express 中实现自定义的 HTTP 方法?

回答重点

在 Express 中实现自定义的 HTTP 方法,可以通过扩展它的路由功能来完成。具体步骤如下:

1)定义一个中间件函数,这个函数会拦截所有请求,并根据请求方法来执行相应的逻辑。 2)在这个中间件中,使用 app.use 来添加路由。 3)通过修改 req 对象的属性,来模拟自定义的 HTTP 方法。例如,我们可以使用 _method 查询参数来传递自定义方法。 4)根据这个参数来决定将请求转发到哪个路由处理函数。

下面是一个简单的示例代码:

const express = require('express');
const app = express();
const port = 3000;
// 自定义 HTTP 方法中间件 app.use((req, res, next) => {
if (req.query._method && req.query._method.toUpperCase() === 'CUSTOM') {
  req.method = 'POST';
  // 例如这里我们将自定义方法 'CUSTOM' 映射为 'POST'
} next();
}
);
// 使用自定义方法的路由 app.post('/custom', (req, res) => {
res.send('Hello, you called a custom method!');
}
);
app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
}
);

在这个例子中,用户可以访问 http://localhost:3000/custom?_method=CUSTOM,而服务器会处理这个请求,就好像它是一个 POST 请求。

扩展知识

为了更深入理解和使用自定义 HTTP 方法,下面了解以下几个相关的扩展知识:

1)RESTful API 设计原则: RESTful API 本身推荐使用标准的 HTTP 方法(GET, POST, PUT, DELETE 等)。理解这些方法的语义、资源地址的设计非常重要。尽量避免滥用和混淆标准方法。

2)中间件的工作机制: 中间件是 Express 的核心部分,它们可以执行一系列任务,包括处理请求、响应、执行任何代码、修改请求和响应对象等。掌握中间件的编写和使用对复杂应用的开发很有帮助。

3)方法重写的库: 有一些第三方库可以帮我们更方便地实现方法重写。例如 method-override 这个中间件,可以让你通过在请求头或者查询参数中指定方法来重写 HTTP 方法,代码会更加简洁。

4)安全和性能考虑: 使用和实现自定义的 HTTP 方法时,应确保这些方法不会带来安全隐患,比如不应允许不必要的操作,需确保验证和权限控制。同样,不要让这些扩展影响到服务器的性能,保持高效处理。

On this page