深入理解 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 应用。

相关推荐
xcya几秒前
Java ReentrantLock 核心用法
后端
用户4665370150513 分钟前
如何在 IntelliJ IDEA 中可视化压缩提交到生产分支
后端·github
小楓120119 分钟前
MySQL數據庫開發教學(一) 基本架構
数据库·后端·mysql
天天摸鱼的java工程师21 分钟前
Java 解析 JSON 文件:八年老开发的实战总结(从业务到代码)
java·后端·面试
白仑色23 分钟前
Spring Boot 全局异常处理
java·spring boot·后端·全局异常处理·统一返回格式
之诺28 分钟前
MySQL通信过程字符集转换
后端·mysql
喵手29 分钟前
反射机制:你真的了解它的“能力”吗?
java·后端·java ee
用户4665370150530 分钟前
git代码压缩合并
后端·github
武大打工仔34 分钟前
从零开始手搓一个MVC框架
后端
开心猴爷39 分钟前
移动端网页调试实战 Cookie 丢失问题的排查与优化
后端