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 应用。