跳到主要内容

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
}
}]
};

参考资源