深入理解 Express.js 路由系统(写个小博客)

Express.js 是一个基于 Node.js 的 web 应用框架,以其简洁、灵活和强大的功能而广受欢迎。路由是 Express.js 的核心功能之一,它定义了应用如何响应客户端请求特定的端点(URI)以及 HTTP 方法(GET、POST 等)。本文将深入探讨 Express.js 路由系统的高级特性和最佳实践,帮助开发者更好地构建高效、可靠和可维护的应用程序。

路由基本概念

在 Express 中,路由是由一个 URI(或者路径)和一个或多个回调函数组成的。基本的路由语法如下:

javascript 复制代码
app.METHOD(PATH, HANDLER)
  • app 是 Express 实例。
  • METHOD 是 HTTP 请求方法(如 GET、POST、PUT、DELETE 等)。
  • PATH 是服务器上的路径。
  • HANDLER 是当路由匹配时执行的函数。

示例

基本路由

javascript 复制代码
const express = require('express');
const app = express();

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

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

上述代码定义了一个基本的 GET 路由,当客户端访问根路径 / 时,服务器会响应 Hello World!

使用不同的 HTTP 方法

javascript 复制代码
app.post('/submit', (req, res) => {
  res.send('Got a POST request');
});

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');
});

这些示例展示了如何处理不同的 HTTP 请求方法。

路由参数

路由参数允许我们捕获 URL 中的动态值,并在请求对象 req 中访问它们。

javascript 复制代码
app.get('/users/:userId/books/:bookId', (req, res) => {
  res.send(req.params);
});

如果客户端访问 /users/42/books/101,服务器将响应:

json 复制代码
{
  "userId": "42",
  "bookId": "101"
}

路由的高级用法

中间件

中间件是 Express.js 最强大的功能之一,它们是具有访问请求对象 (req)、响应对象 (res) 和应用程序请求-响应循环中下一个中间件函数的函数。中间件可以执行以下任务:

  • 执行任何代码。
  • 修改请求和响应对象。
  • 终结请求-响应循环。
  • 调用堆栈中的下一个中间件。

应用级中间件

应用级中间件绑定到 app 实例上,使用 app.use()app.METHOD()

javascript 复制代码
const express = require('express');
const app = express();

app.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

app.get('/', (req, res) => {
  res.send('Home Page');
});

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

路由级中间件

路由级中间件绑定到 express.Router 实例上。

javascript 复制代码
const router = express.Router();

router.use((req, res, next) => {
  console.log('Request URL:', req.originalUrl);
  next();
});

router.get('/', (req, res) => {
  res.send('Home Page');
});

app.use('/', router);

异步处理

在处理异步操作时,可以使用 async/await 语法和错误处理中间件来管理异步代码中的错误。

javascript 复制代码
app.get('/user/:id', async (req, res, next) => {
  try {
    const user = await getUserById(req.params.id); // 假设这是一个异步函数
    res.send(user);
  } catch (err) {
    next(err);
  }
});

路由参数验证和处理

Express.js 并没有内置的路由参数验证功能,但可以使用中间件来处理参数验证。结合 express-validator 这样的库,可以实现更强大的验证逻辑。

javascript 复制代码
const { check, validationResult } = require('express-validator');

app.post('/user', [
  check('username').isEmail(),
  check('password').isLength({ min: 5 })
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  res.send('User is valid');
});

嵌套路由

嵌套路由有助于创建模块化的路由系统,尤其是在大型应用程序中。

javascript 复制代码
const express = require('express');
const app = express();
const userRouter = express.Router();
const adminRouter = express.Router();

userRouter.get('/', (req, res) => {
  res.send('User Home Page');
});

userRouter.get('/profile', (req, res) => {
  res.send('User Profile Page');
});

adminRouter.get('/', (req, res) => {
  res.send('Admin Home Page');
});

adminRouter.get('/dashboard', (req, res) => {
  res.send('Admin Dashboard');
});

app.use('/user', userRouter);
app.use('/admin', adminRouter);

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

错误处理

Express 提供了一个默认的错误处理中间件,但你可以定义自己的错误处理逻辑。

javascript 复制代码
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

结合异步处理:

javascript 复制代码
app.get('/user/:id', async (req, res, next) => {
  try {
    const user = await getUserById(req.params.id);
    res.send(user);
  } catch (err) {
    next(err);
  }
});

app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});

综合应用示例

为了更好地理解这些概念,我们将通过一个实际的示例来展示如何在真实项目中应用这些技术。假设我们要构建一个简单的博客系统,包括用户注册、登录、文章创建和获取文章的功能。

项目结构

首先,我们定义项目的基本结构:

lua 复制代码
blog-app/
├── app.js
├── routes/
│   ├── index.js
│   ├── users.js
│   └── posts.js
├── models/
│   └── user.js
│   └── post.js
├── controllers/
│   └── userController.js
│   └── postController.js
├── middlewares/
│   └── auth.js
├── config/
│   └── db.js
└── package.json

1. 初始化项目

首先,初始化一个新的 Node.js 项目并安装所需的依赖:

bash 复制代码
npm init -y
npm install express mongoose bcryptjs jsonwebtoken express-validator

2. 设置数据库连接

config/db.js 中配置 MongoDB 连接:

javascript 复制代码
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect('mongodb://localhost:27017/blog', {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log('MongoDB connected...');
  } catch (err) {
    console.error(err.message);
    process.exit(1);
  }
};

module.exports = connectDB;

3. 定义数据模型

models/user.js 中定义用户模型:

javascript 复制代码
const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
});

module.exports = mongoose.model('User', UserSchema);

models/post.js 中定义文章模型:

javascript 复制代码
const mongoose = require('mongoose');

const PostSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
  content: {
    type: String,
    required: true,
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true,
  },
});

module.exports = mongoose.model('Post', PostSchema);

4. 创建控制器

controllers/userController.js 中定义用户注册和登录逻辑:

javascript 复制代码
const User = require('../models/user');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { validationResult } = require('express-validator');

exports.register = async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const { username, password } = req.body;

  try {
    let user = await User.findOne({ username });
    if (user) {
      return res.status(400).json({ msg: 'User already exists' });
    }

    user = new User({ username, password });

    const salt = await bcrypt.genSalt(10);
    user.password = await bcrypt.hash(password, salt);

    await user.save();

    const payload = {
      user: {
        id: user.id,
      },
    };

    jwt.sign(payload, 'secret', { expiresIn: 360000 }, (err, token) => {
      if (err) throw err;
      res.json({ token });
    });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};

exports.login = async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const { username, password } = req.body;

  try {
    let user = await User.findOne({ username });
    if (!user) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    const payload = {
      user: {
        id: user.id,
      },
    };

    jwt.sign(payload, 'secret', { expiresIn: 360000 }, (err, token) => {
      if (err) throw err;
      res.json({ token });
    });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};

controllers/postController.js 中定义文章创建和获取逻辑:

javascript 复制代码
const Post = require('../models/post');
const { validationResult } = require('express-validator');

exports.createPost = async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const { title, content } = req.body;

  try {
    const newPost = new Post({
      title,
      content,
      author: req.user.id,
    });

    const post = await newPost.save();
    res.json(post);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};

exports.getPosts = async (req, res) => {
  try {
    const posts = await Post.find().populate('author', ['username']);
    res.json(posts);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};

5. 定义中间件

middlewares/auth.js 中定义认证中间件:

javascript 复制代码
const jwt = require('jsonwebtoken');

module.exports = (req, res, next) => {
  const token = req.header('x-auth-token');

  if (!token) {
    return res.status(401).json({ msg: 'No token, authorization denied' });
  }

  try {
    const decoded = jwt.verify(token, 'secret');
    req.user = decoded.user;
    next();
  } catch (err) {
    res.status(401).json({ msg: 'Token is not valid' });
  }
};

6. 创建路由

routes/users.js 中定义用户路由:

javascript 复制代码
const express = require('express');
const router = express.Router();
const { check } = require('express-validator');
const userController = require('../controllers/userController');

// 用户注册
router.post(
  '/register',
  [
    check('username', 'Username is required').not().isEmpty(),
    check('password', 'Password is required').isLength({ min: 6 }),
  ],
  userController.register
);

// 用户登录
router.post(
  '/login',
  [
    check('username', 'Username is required').not().isEmpty(),
    check('password', 'Password is required').exists(),
  ],
  userController.login
);

module.exports = router;

routes/posts.js 中定义文章路由:

javascript 复制代码
const express = require('express');
const router = express.Router();
const { check } = require('express-validator');
const postController = require('../controllers/postController');
const auth = require('../middlewares/auth');

// 创建文章
router.post(
  '/',
  [
    auth,
    [
      check('title', 'Title is required').not().isEmpty(),
      check('content', 'Content is required').not().isEmpty(),
    ],
  ],
  postController.createPost
);

// 获取所有文章
router.get('/', postController.getPosts);

module.exports = router;

7. 设置主应用

app.js 中设置 Express 应用:

javascript 复制代码
const express = require('express');
const connectDB = require('./config/db');

const app = express();

// 连接数据库
connectDB();

// 初始化中间件
app.use(express.json({ extended: false }));

// 定义路由
app.use('/api/users', require('./routes/users'));
app.use('/api/posts', require('./routes/posts'));

const PORT = process.env.PORT || 5000;

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

8. 启动应用

运行应用程序:

bash 复制代码
node app.js

现在,你可以通过以下 API 端点与应用程序进行交互:

  • POST /api/users/register - 用户注册
  • POST /api/users/login - 用户登录
  • POST /api/posts - 创建文章(需要认证)
  • GET /api/posts - 获取所有文章

这个示例展示了如何使用 Express.js 构建一个简单的博客系统,包括用户认证、文章创建和获取的功能,结合了路由、中间件、异步处理、路由参数验证和错误处理等高级特性。

总结

通过深入理解和应用 Express.js 的中间件、异步处理、路由参数验证、嵌套路由和错误处理功能,可以开发出更加健壮和可维护的 web 应用程序。这些高级特性和最佳实践能够显著提升应用的性能和可靠性。希望这篇文章能帮助你更好地掌握 Express.js 路由系统,构建出更加优质的 web 应用。

相关推荐
小_太_阳38 分钟前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
搬码后生仔2 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
凡人的AI工具箱2 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
Lx3523 小时前
Pandas数据重命名:列名与索引为标题
后端·python·pandas
小池先生3 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
百罹鸟3 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
小蜗牛慢慢爬行4 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
wm10435 小时前
java web springboot
java·spring boot·后端
龙少95437 小时前
【深入理解@EnableCaching】
java·后端·spring