Koa.js
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造,致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。
1. 基础使用
1.1 安装和设置
# 创建新项目
mkdir koa-app
cd koa-app
npm init -y
# 安装 Koa
npm install koa
# 安装常用中间件
npm install koa-router koa-bodyparser koa-logger koa-cors koa-helmet
1.2 基本应用
// app.js
const Koa = require('koa');
const app = new Koa();
const port = 3000;
// 中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
ctx.app.emit('error', err, ctx);
}
});
// 路由
app.use(async ctx => {
ctx.body = 'Hello World';
});
// 错误处理
app.on('error', (err, ctx) => {
console.error('server error', err);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
2. 路由系统
2.1 基础路由
// routes/users.js
const Router = require('koa-router');
const router = new Router({
prefix: '/users'
});
// GET /users
router.get('/', async (ctx) => {
ctx.body = [{ id: 1, name: 'John' }];
});
// GET /users/:id
router.get('/:id', async (ctx) => {
const { id } = ctx.params;
ctx.body = { id, name: 'John' };
});
// POST /users
router.post('/', async (ctx) => {
const user = ctx.request.body;
ctx.status = 201;
ctx.body = user;
});
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use(usersRouter.routes());
app.use(usersRouter.allowedMethods());
2.2 嵌套路由
const Router = require('koa-router');
const forums = new Router({
prefix: '/forums'
});
const posts = new Router({
prefix: '/:forumId/posts'
});
posts.get('/', async (ctx) => {
ctx.body = `Posts in forum ${ctx.params.forumId}`;
});
posts.post('/', async (ctx) => {
ctx.body = `Create post in forum ${ctx.params.forumId}`;
});
forums.use(posts.routes(), posts.allowedMethods());
app.use(forums.routes());
3. 中间件
3.1 常用中间件
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const logger = require('koa-logger');
const cors = require('@koa/cors');
const helmet = require('koa-helmet');
const serve = require('koa-static');
const app = new Koa();
// 日志中间件
app.use(logger());
// 安全中间件
app.use(helmet());
// CORS 中间件
app.use(cors());
// Body 解析
app.use(bodyParser());
// 静态文件服务
app.use(serve('./public'));
// 自定义中间件
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
3.2 错误处理中间件
// middleware/error.js
module.exports = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: {
message: err.message,
status: ctx.status,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
}
};
ctx.app.emit('error', err, ctx);
}
};
// 自定义错误
class AppError extends Error {
constructor(message, status = 500) {
super(message);
this.status = status;
}
}
4. 数据库集成
4.1 MongoDB (Mongoose)
const mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost/koa-app', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// 定义模型
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
});
const User = mongoose.model('User', userSchema);
// 使用模型
router.post('/users', async (ctx) => {
try {
const user = new User(ctx.request.body);
await user.save();
ctx.status = 201;
ctx.body = user;
} catch (error) {
ctx.throw(400, error.message);
}
});
4.2 SQL (Sequelize)
const { Sequelize, DataTypes } = require('sequelize');
// 连接数据库
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'
});
// 定义模型
const User = sequelize.define('User', {
name: DataTypes.STRING,
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
}
});
// 使用模型
router.post('/users', async (ctx) => {
try {
const user = await User.create(ctx.request.body);
ctx.status = 201;
ctx.body = user;
} catch (error) {
ctx.throw(400, error.message);
}
});
5. 认证和授权
5.1 JWT 认证
const jwt = require('jsonwebtoken');
// 认证中间件
async function auth(ctx, next) {
try {
const token = ctx.header.authorization.replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
ctx.state.user = decoded;
await next();
} catch (error) {
ctx.throw(401, 'Please authenticate');
}
}
// 登录路由
router.post('/login', async (ctx) => {
const { email, password } = ctx.request.body;
const user = await User.findOne({ email });
if (!user || !await user.comparePassword(password)) {
ctx.throw(401, 'Invalid credentials');
}
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET);
ctx.body = { token };
});
// 受保护的路由
router.get('/profile', auth, async (ctx) => {
const user = await User.findById(ctx.state.user.id);
ctx.body = user;
});
5.2 Session 认证
const session = require('koa-session');
app.keys = [process.env.SESSION_SECRET];
const CONFIG = {
key: 'koa.sess',
maxAge: 86400000,
autoCommit: true,
overwrite: true,
httpOnly: true,
signed: true,
rolling: false,
renew: false,
secure: process.env.NODE_ENV === 'production'
};
app.use(session(CONFIG, app));
// 登录路由
router.post('/login', async (ctx) => {
const { email, password } = ctx.request.body;
const user = await User.findOne({ email });
if (user && await user.comparePassword(password)) {
ctx.session.userId = user.id;
ctx.body = { message: 'Logged in successfully' };
} else {
ctx.throw(401, 'Invalid credentials');
}
});
6. 测试
6.1 单元测试
// test/user.test.js
const request = require('supertest');
const app = require('../app');
const User = require('../models/user');
describe('User API', () => {
beforeEach(async () => {
await User.deleteMany();
});
test('Should create new user', async () => {
const response = await request(app.callback())
.post('/users')
.send({
name: 'Test User',
email: 'test@example.com',
password: 'password123'
})
.expect(201);
const user = await User.findById(response.body._id);
expect(user).not.toBeNull();
});
});
6.2 集成测试
// test/auth.test.js
describe('Authentication', () => {
let token;
beforeEach(async () => {
const response = await request(app.callback())
.post('/login')
.send({
email: 'test@example.com',
password: 'password123'
});
token = response.body.token;
});
test('Should get profile for authenticated user', async () => {
await request(app.callback())
.get('/profile')
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
});
7. 部署
7.1 生产 环境配置
// config/production.js
module.exports = {
port: process.env.PORT || 3000,
mongodb: {
uri: process.env.MONGODB_URI
},
jwt: {
secret: process.env.JWT_SECRET
},
cors: {
origin: process.env.CORS_ORIGIN,
credentials: true
}
};
// 应用配置
if (process.env.NODE_ENV === 'production') {
app.use(helmet());
app.use(compress());
app.use(logger());
}
7.2 PM2 部署
// ecosystem.config.js
module.exports = {
apps: [{
name: 'koa-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env_production: {
NODE_ENV: 'production',
PORT: 80
}
}]
};