文章目录
-
- [什么是 Koa.js?](#什么是 Koa.js?)
- [为什么选择 Koa?](#为什么选择 Koa?)
-
- [Koa 与 Express 的对比](#Koa 与 Express 的对比)
- [Koa 的优势](#Koa 的优势)
- 环境准备与安装
-
- [1. 系统要求](#1. 系统要求)
- [2. 创建 Koa 项目](#2. 创建 Koa 项目)
- [3. 基础项目结构](#3. 基础项目结构)
- [Koa 核心概念](#Koa 核心概念)
-
- [1. 应用程序 (Application)](#1. 应用程序 (Application))
- [2. 上下文 (Context)](#2. 上下文 (Context))
- [3. 请求 (Request) 和响应 (Response)](#3. 请求 (Request) 和响应 (Response))
- 中间件详解
-
- [1. 洋葱模型](#1. 洋葱模型)
- [2. 常用内置中间件](#2. 常用内置中间件)
- [3. 自定义中间件](#3. 自定义中间件)
- 路由处理
-
- [1. 使用 koa-router](#1. 使用 koa-router)
- [2. 路由模块化](#2. 路由模块化)
- 常用中间件
-
- [1. 第三方中间件安装](#1. 第三方中间件安装)
- [2. 中间件配置示例](#2. 中间件配置示例)
- 数据库集成
-
- [1. MySQL 集成](#1. MySQL 集成)
- [2. 用户服务示例](#2. 用户服务示例)
- 错误处理
-
- [1. 全局错误处理](#1. 全局错误处理)
- [2. 自定义错误类](#2. 自定义错误类)
- 认证与授权
-
- [1. JWT 认证](#1. JWT 认证)
- [2. 密码加密](#2. 密码加密)
- [3. 认证控制器](#3. 认证控制器)
- 总结
什么是 Koa.js?
Koa.js 是一个由 Express 原班人马打造的下一代 Node.js Web 框架,旨在成为一个更小、更富有表现力、更健壮的 Web 框架。通过利用 async 函数,Koa 可以帮助你丢弃回调函数,并极大地增强错误处理的能力。
Koa 没有捆绑任何中间件,而是提供了一套优雅的方法来编写服务器端应用程序,让开发者可以自由地选择所需的中间件组件。
- Koa 是一个轻量级的 Node.js Web 框架,核心代码只有约 600 行
- 采用洋葱模型中间件机制,执行顺序更加清晰
- 完全基于 Promise 和 async/await,避免回调地狱
- 具有更好的错误处理机制
为什么选择 Koa?
Koa 与 Express 的对比
特性 | Express | Koa |
---|---|---|
中间件机制 | 线性执行 | 洋葱模型 |
异步处理 | 回调函数 | async/await |
错误处理 | 回调错误 | Try/Catch |
体积大小 | 相对较大 | 非常轻量 |
学习曲线 | 平缓 | 需要 Promise 知识 |
灵活性 | 内置功能多 | 高度可定制 |
Koa 的优势
- 更现代的异步处理:基于 async/await,代码更清晰
- 更好的错误处理:使用 try/catch 捕获错误
- 轻量级设计:核心简洁,按需添加中间件
- 洋葱模型:中间件执行顺序更可控
- ES6+ 特性:充分利用现代 JavaScript 特性
环境准备与安装
1. 系统要求
- Node.js 版本 7.6.0 或更高(支持 async/await)
- npm 或 yarn 包管理器
2. 创建 Koa 项目
bash
# 创建项目目录
mkdir koa-app
cd koa-app
# 初始化 package.json
pnpm init
# 安装 Koa
pnpm i koa
# 安装开发依赖(可选)
pnpm i -D nodemon
3. 基础项目结构
koa-app/
├── package.json
├── app.js
├── src/
│ ├── controllers/
│ ├── middleware/
│ ├── routes/
│ ├── utils/
│ └── config/
├── public/
└── views/
Koa 核心概念
1. 应用程序 (Application)
基础 Koa 应用:
javascript
// app.js
const Koa = require('koa');
const app = new Koa();
// 最简单的中间件
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// 响应中间件
app.use(async (ctx) => {
ctx.body = 'Hello Koa!';
});
// 启动服务器
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
启动应用:
bash
node app.js
# 或使用 nodemon
npx nodemon app.js
2. 上下文 (Context)
Context 对象包含了请求和响应的所有信息:
javascript
app.use(async (ctx) => {
// 请求信息
console.log('Method:', ctx.method); // 请求方法
console.log('URL:', ctx.url); // 请求 URL
console.log('Path:', ctx.path); // 请求路径
console.log('Query:', ctx.query); // 查询参数
console.log('Headers:', ctx.headers); // 请求头
// 响应控制
ctx.status = 200; // 状态码
ctx.type = 'application/json'; // 内容类型
ctx.body = { // 响应体
message: 'Hello Koa!',
timestamp: new Date().toISOString()
};
// 设置响应头
ctx.set('X-Custom-Header', 'custom-value');
});
3. 请求 (Request) 和响应 (Response)
Request 对象:
javascript
app.use(async (ctx) => {
const { request } = ctx;
// 请求属性
console.log('Method:', request.method);
console.log('URL:', request.url);
console.log('Original URL:', request.originalUrl);
console.log('Origin:', request.origin);
console.log('Path:', request.path);
console.log('Query:', request.query);
console.log('Querystring:', request.querystring);
console.log('Host:', request.host);
console.log('Hostname:', request.hostname);
console.log('Fresh:', request.fresh);
console.log('Stale:', request.stale);
console.log('Protocol:', request.protocol);
console.log('Secure:', request.secure);
console.log('IP:', request.ip);
console.log('IPs:', request.ips);
console.log('Subdomains:', request.subdomains);
// 请求头
console.log('Headers:', request.headers);
console.log('Content-Type:', request.type);
console.log('Content-Length:', request.length);
console.log('Accept:', request.accepts());
// 获取特定头信息
console.log('User-Agent:', request.get('User-Agent'));
});
Response 对象:
javascript
app.use(async (ctx) => {
const { response } = ctx;
// 设置状态码
response.status = 201;
// 设置响应头
response.set('X-Powered-By', 'Koa');
response.set({
'X-Custom-Header': 'value1',
'X-Another-Header': 'value2'
});
// 设置内容类型
response.type = 'application/json';
response.type = 'text/html; charset=utf-8';
// 设置响应体
response.body = { message: 'Hello World' };
response.body = 'Hello World';
response.body = Buffer.from('Hello World');
// 重定向
response.redirect('/new-location');
response.redirect('https://example.com');
// 附件下载
response.attachment('filename.txt');
// 最后修改时间
response.lastModified = new Date();
// ETag
response.etag = 'hash-string';
});
中间件详解
1. 洋葱模型
Koa 中间件的执行顺序:
javascript
app.use(async (ctx, next) => {
console.log('1. 开始');
await next();
console.log('6. 结束');
});
app.use(async (ctx, next) => {
console.log('2. 进入');
await next();
console.log('5. 退出');
});
app.use(async (ctx, next) => {
console.log('3. 核心处理');
ctx.body = 'Hello World';
console.log('4. 响应设置');
});
// 执行顺序: 1 → 2 → 3 → 4 → 5 → 6
2. 常用内置中间件
javascript
const Koa = require('koa');
const app = new Koa();
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : {}
};
ctx.app.emit('error', err, ctx);
}
});
// 日志中间件
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// 响应时间中间件
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// 静态文件服务(需要 koa-static)
// app.use(require('koa-static')('public'));
3. 自定义中间件
认证中间件:
javascript
// middleware/auth.js
const auth = () => {
return async (ctx, next) => {
const token = ctx.headers.authorization;
if (!token) {
ctx.throw(401, 'No token provided');
}
try {
// 验证 token(这里简化处理)
const user = await verifyToken(token.replace('Bearer ', ''));
ctx.state.user = user;
await next();
} catch (error) {
ctx.throw(401, 'Invalid token');
}
};
};
async function verifyToken(token) {
// 实际项目中会使用 JWT 等验证机制
return { id: 1, username: 'admin' };
}
module.exports = auth;
参数验证中间件:
javascript
// middleware/validator.js
const validate = (rules) => {
return async (ctx, next) => {
const errors = [];
// 验证查询参数
if (rules.query) {
for (const [key, rule] of Object.entries(rules.query)) {
const value = ctx.query[key];
if (rule.required && (value === undefined || value === '')) {
errors.push(`${key} is required`);
}
if (value && rule.type && typeof value !== rule.type) {
errors.push(`${key} should be ${rule.type}`);
}
}
}
// 验证请求体
if (rules.body) {
for (const [key, rule] of Object.entries(rules.body)) {
const value = ctx.request.body[key];
if (rule.required && (value === undefined || value === '')) {
errors.push(`${key} is required`);
}
if (value && rule.type && typeof value !== rule.type) {
errors.push(`${key} should be ${rule.type}`);
}
}
}
if (errors.length > 0) {
ctx.throw(400, `Validation failed: ${errors.join(', ')}`);
}
await next();
};
};
module.exports = validate;
路由处理
1. 使用 koa-router
安装:
bash
pnpm i koa-router
基础路由:
javascript
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 基本路由
router.get('/', async (ctx) => {
ctx.body = 'Home Page';
});
router.get('/about', async (ctx) => {
ctx.body = 'About Page';
});
// 路由参数
router.get('/users/:id', async (ctx) => {
ctx.body = `User ID: ${ctx.params.id}`;
});
// 查询参数
router.get('/search', async (ctx) => {
const { q, page = 1 } = ctx.query;
ctx.body = {
query: q,
page: parseInt(page),
results: [`Result 1 for ${q}`, `Result 2 for ${q}`]
};
});
// POST 请求
router.post('/users', async (ctx) => {
const userData = ctx.request.body;
ctx.status = 201;
ctx.body = { message: 'User created', id: Date.now() };
});
// PUT 请求
router.put('/users/:id', async (ctx) => {
const userId = ctx.params.id;
const userData = ctx.request.body;
ctx.body = { message: `User ${userId} updated` };
});
// DELETE 请求
router.delete('/users/:id', async (ctx) => {
const userId = ctx.params.id;
ctx.status = 204;
});
// 注册路由
app.use(router.routes());
app.use(router.allowedMethods());
2. 路由模块化
routes/users.js:
javascript
const Router = require('koa-router');
const router = new Router({ prefix: '/users' });
// 获取所有用户
router.get('/', async (ctx) => {
ctx.body = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
});
// 获取单个用户
router.get('/:id', async (ctx) => {
const userId = ctx.params.id;
ctx.body = { id: userId, name: `User ${userId}` };
});
// 创建用户
router.post('/', async (ctx) => {
const userData = ctx.request.body;
ctx.status = 201;
ctx.body = { message: 'User created', id: Date.now(), ...userData };
});
// 更新用户
router.put('/:id', async (ctx) => {
const userId = ctx.params.id;
const userData = ctx.request.body;
ctx.body = { message: `User ${userId} updated`, ...userData };
});
// 删除用户
router.delete('/:id', async (ctx) => {
const userId = ctx.params.id;
ctx.status = 204;
});
module.exports = router;
主应用文件:
javascript
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const userRoutes = require('./routes/users');
const app = new Koa();
// 中间件
app.use(bodyParser());
// 路由
app.use(userRoutes.routes());
app.use(userRoutes.allowedMethods());
app.listen(3000);
常用中间件
1. 第三方中间件安装
bash
# 请求体解析
pnpm i koa-bodyparser
# 静态文件服务
pnpm i koa-static
# 会话管理
pnpm i koa-session
# CORS 支持
pnpm i @koa/cors
# 速率限制
pnpm i koa-ratelimit
# 压缩
pnpm i koa-compress
# 安全头
pnpm i koa-helmet
# JWT 认证
pnpm i koa-jwt jsonwebtoken
2. 中间件配置示例
javascript
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const static = require('koa-static');
const cors = require('@koa/cors');
const compress = require('koa-compress');
const helmet = require('koa-helmet');
const session = require('koa-session');
const app = new Koa();
// 安全头
app.use(helmet());
// CORS
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));
// 压缩
app.use(compress({
threshold: 2048,
gzip: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
deflate: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
br: false
}));
// 会话配置
app.keys = ['your-session-secret'];
app.use(session({
key: 'koa.sess',
maxAge: 86400000,
overwrite: true,
httpOnly: true,
signed: true,
rolling: false,
renew: false
}, app));
// 请求体解析
app.use(bodyParser({
enableTypes: ['json', 'form', 'text'],
formLimit: '10mb',
jsonLimit: '10mb'
}));
// 静态文件服务
app.use(static('public'));
// 错误处理
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
};
ctx.app.emit('error', err, ctx);
}
});
// 应用错误事件监听
app.on('error', (err, ctx) => {
console.error('Server error:', err);
});
数据库集成
1. MySQL 集成
安装依赖:
bash
pnpm i mysql2
数据库配置:
javascript
// config/database.js
const mysql = require('mysql2/promise');
const dbConfig = {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'koa_app',
connectionLimit: 10,
acquireTimeout: 60000,
timeout: 60000,
};
const pool = mysql.createPool(dbConfig);
// 查询封装
const query = async (sql, params = []) => {
const connection = await pool.getConnection();
try {
const [results] = await connection.execute(sql, params);
return results;
} finally {
connection.release();
}
};
module.exports = {
pool,
query
};
2. 用户服务示例
javascript
// services/userService.js
const { query } = require('../config/database');
class UserService {
// 获取所有用户
async findAll() {
const sql = 'SELECT * FROM users WHERE deleted_at IS NULL';
return await query(sql);
}
// 根据ID查找用户
async findById(id) {
const sql = 'SELECT * FROM users WHERE id = ? AND deleted_at IS NULL';
const users = await query(sql, [id]);
return users[0] || null;
}
// 根据邮箱查找用户
async findByEmail(email) {
const sql = 'SELECT * FROM users WHERE email = ? AND deleted_at IS NULL';
const users = await query(sql, [email]);
return users[0] || null;
}
// 创建用户
async create(userData) {
const { username, email, password } = userData;
const sql = `
INSERT INTO users (username, email, password, created_at, updated_at)
VALUES (?, ?, ?, NOW(), NOW())
`;
const result = await query(sql, [username, email, password]);
return this.findById(result.insertId);
}
// 更新用户
async update(id, userData) {
const fields = [];
const values = [];
Object.keys(userData).forEach(key => {
if (userData[key] !== undefined) {
fields.push(`${key} = ?`);
values.push(userData[key]);
}
});
if (fields.length === 0) {
return this.findById(id);
}
values.push(id);
const sql = `
UPDATE users
SET ${fields.join(', ')}, updated_at = NOW()
WHERE id = ? AND deleted_at IS NULL
`;
await query(sql, values);
return this.findById(id);
}
// 删除用户(软删除)
async delete(id) {
const sql = 'UPDATE users SET deleted_at = NOW() WHERE id = ?';
await query(sql, [id]);
}
}
module.exports = new UserService();
错误处理
1. 全局错误处理
javascript
// middleware/errorHandler.js
const errorHandler = () => {
return async (ctx, next) => {
try {
await next();
// 处理 404
if (ctx.status === 404 && !ctx.body) {
ctx.throw(404, 'Resource not found');
}
} catch (err) {
ctx.status = err.status || 500;
// 开发环境返回详细错误信息
if (process.env.NODE_ENV === 'development') {
ctx.body = {
error: err.message,
stack: err.stack,
status: ctx.status
};
} else {
// 生产环境返回通用错误信息
ctx.body = {
error: ctx.status === 500 ? 'Internal Server Error' : err.message,
status: ctx.status
};
}
// 触发错误事件
ctx.app.emit('error', err, ctx);
}
};
};
module.exports = errorHandler;
2. 自定义错误类
javascript
// utils/AppError.js
class AppError extends Error {
constructor(message, status = 500, code = null) {
super(message);
this.name = this.constructor.name;
this.status = status;
this.code = code;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message = 'Validation failed') {
super(message, 400, 'VALIDATION_ERROR');
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404, 'NOT_FOUND');
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401, 'UNAUTHORIZED');
}
}
class ForbiddenError extends AppError {
constructor(message = 'Forbidden') {
super(message, 403, 'FORBIDDEN');
}
}
module.exports = {
AppError,
ValidationError,
NotFoundError,
UnauthorizedError,
ForbiddenError
};
认证与授权
1. JWT 认证
javascript
// middleware/jwtAuth.js
const jwt = require('jsonwebtoken');
const { UnauthorizedError } = require('../utils/AppError');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
const jwtAuth = () => {
return async (ctx, next) => {
const authHeader = ctx.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedError('No token provided');
}
const token = authHeader.slice(7);
try {
const decoded = jwt.verify(token, JWT_SECRET);
ctx.state.user = decoded;
await next();
} catch (error) {
throw new UnauthorizedError('Invalid token');
}
};
};
// 生成 JWT Token
const generateToken = (payload, options = {}) => {
return jwt.sign(payload, JWT_SECRET, {
expiresIn: '7d',
...options
});
};
module.exports = {
jwtAuth,
generateToken
};
2. 密码加密
javascript
// utils/password.js
const bcrypt = require('bcryptjs');
const saltRounds = 12;
// 加密密码
const hashPassword = async (password) => {
return await bcrypt.hash(password, saltRounds);
};
// 验证密码
const verifyPassword = async (password, hashedPassword) => {
return await bcrypt.compare(password, hashedPassword);
};
module.exports = {
hashPassword,
verifyPassword
};
3. 认证控制器
javascript
// controllers/authController.js
const userService = require('../services/userService');
const { generateToken } = require('../middleware/jwtAuth');
const { hashPassword, verifyPassword } = require('../utils/password');
const { ValidationError, UnauthorizedError } = require('../utils/AppError');
class AuthController {
// 用户注册
async register(ctx) {
const { username, email, password } = ctx.request.body;
// 验证输入
if (!username || !email || !password) {
throw new ValidationError('Username, email and password are required');
}
// 检查用户是否已存在
const existingUser = await userService.findByEmail(email);
if (existingUser) {
throw new ValidationError('User already exists');
}
// 加密密码
const hashedPassword = await hashPassword(password);
// 创建用户
const user = await userService.create({
username,
email,
password: hashedPassword
});
// 生成 token
const token = generateToken({
userId: user.id,
email: user.email
});
ctx.status = 201;
ctx.body = {
message: 'User registered successfully',
user: {
id: user.id,
username: user.username,
email: user.email
},
token
};
}
// 用户登录
async login(ctx) {
const { email, password } = ctx.request.body;
if (!email || !password) {
throw new ValidationError('Email and password are required');
}
// 查找用户
const user = await userService.findByEmail(email);
if (!user) {
throw new UnauthorizedError('Invalid credentials');
}
// 验证密码
const isValidPassword = await verifyPassword(password, user.password);
if (!isValidPassword) {
throw new UnauthorizedError('Invalid credentials');
}
// 生成 token
const token = generateToken({
userId: user.id,
email: user.email
});
ctx.body = {
message: 'Login successful',
user: {
id: user.id,
username: user.username,
email: user.email
},
token
};
}
// 获取当前用户信息
async getCurrentUser(ctx) {
const user = await userService.findById(ctx.state.user.userId);
if (!user) {
throw new UnauthorizedError('User not found');
}
ctx.body = {
user: {
id: user.id,
username: user.username,
email: user.email
}
};
}
}
module.exports = new AuthController();
总结
Koa.js 作为一个现代、轻量级的 Node.js Web 框架,通过其洋葱模型中间件机制和基于 async/await 的异步处理,为开发者提供了更加优雅和高效的开发体验。相比 Express,Koa 在错误处理、中间件控制和代码可读性方面都有显著提升。
本文详细介绍了 Koa 的核心概念、中间件机制、路由处理、数据库集成、认证授权以及实际项目开发的全过程。掌握这些知识后,你将能够使用 Koa 构建出高性能、可维护的 Web 应用程序。
Koa 的轻量级设计和高度可定制性使其成为构建现代 Web 应用的理想选择。随着对框架的深入理解,你可以进一步探索 Koa 与各种数据库、缓存、消息队列等技术的集成,构建出更加复杂和强大的应用系统。