Koa 示例
Koa Node.js 中间件框架示例,包括中间件链、上下文处理、async/await 模式和生态系统集成
💻 Koa 你好世界 javascript
🟢 simple
基础 Koa 服务器设置,包含中间件、路由、async 处理器和现代 JavaScript 模式
// Koa Hello World Examples
// 1. Basic Koa Server
const Koa = require('koa');
const app = new Koa();
// Middleware function
app.use(async ctx => {
ctx.body = 'Hello Koa!';
});
app.listen(3000);
console.log('Server running on http://localhost:3000');
// 2. Koa with Multiple Middleware
const Koa = require('koa');
const app = new Koa();
// Logger middleware
app.use(async (ctx, next) => {
console.log(`${new Date().toISOString()} ${ctx.method} ${ctx.url}`);
await next();
console.log(`Response status: ${ctx.status}`);
});
// Error handling middleware
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);
}
});
// Response time middleware
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// Route handler
app.use(async ctx => {
ctx.body = {
message: 'Hello from Koa with middleware!',
method: ctx.method,
url: ctx.url,
headers: ctx.headers
};
});
app.listen(3000);
console.log('Server with middleware running on http://localhost:3000');
// 3. Koa Router with REST API
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router();
// Middleware
app.use(bodyParser());
// Mock data store
let users = [
{ id: 1, name: 'John Doe', email: '[email protected]', age: 30 },
{ id: 2, name: 'Jane Smith', email: '[email protected]', age: 25 }
];
// Routes
router.get('/api/users', async (ctx) => {
ctx.body = { users, total: users.length };
});
router.get('/api/users/:id', async (ctx) => {
const id = parseInt(ctx.params.id);
const user = users.find(u => u.id === id);
if (!user) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
return;
}
ctx.body = user;
});
router.post('/api/users', async (ctx) => {
const { name, email, age } = ctx.request.body;
if (!name || !email) {
ctx.status = 400;
ctx.body = { error: 'Name and email are required' };
return;
}
const newUser = {
id: users.length + 1,
name,
email,
age: age || null
};
users.push(newUser);
ctx.status = 201;
ctx.body = newUser;
});
router.put('/api/users/:id', async (ctx) => {
const id = parseInt(ctx.params.id);
const userIndex = users.findIndex(u => u.id === id);
if (userIndex === -1) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
return;
}
users[userIndex] = { ...users[userIndex], ...ctx.request.body };
ctx.body = users[userIndex];
});
router.delete('/api/users/:id', async (ctx) => {
const id = parseInt(ctx.params.id);
const userIndex = users.findIndex(u => u.id === id);
if (userIndex === -1) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
return;
}
users.splice(userIndex, 1);
ctx.status = 204;
});
// Health check
router.get('/health', async (ctx) => {
ctx.body = {
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime()
};
});
// Mount routes
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
console.log('REST API server running on http://localhost:3000');
// 4. Koa with Custom Middleware
const Koa = require('koa');
const app = new Koa();
// Custom authentication middleware
const authMiddleware = (options = {}) => {
return async (ctx, next) => {
// Skip authentication for certain paths
if (ctx.path.startsWith('/public') || ctx.path === '/') {
await next();
return;
}
const token = ctx.headers.authorization?.replace('Bearer ', '');
if (!token) {
ctx.status = 401;
ctx.body = { error: 'No token provided' };
return;
}
// Mock authentication (use proper JWT validation in production)
if (token !== 'valid-token') {
ctx.status = 401;
ctx.body = { error: 'Invalid token' };
return;
}
// Set user in context
ctx.state.user = { id: 1, name: 'John Doe', role: 'user' };
await next();
};
};
// Custom CORS middleware
const corsMiddleware = (options = {}) => {
const defaults = {
origin: '*',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization']
};
const config = { ...defaults, ...options };
return async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', config.origin);
ctx.set('Access-Control-Allow-Methods', config.allowMethods.join(', '));
ctx.set('Access-Control-Allow-Headers', config.allowHeaders.join(', '));
if (ctx.method === 'OPTIONS') {
ctx.status = 204;
return;
}
await next();
};
};
// Custom rate limiting middleware
const rateLimitMiddleware = (options = {}) => {
const defaults = {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later'
};
const config = { ...defaults, ...options };
const requests = new Map();
return async (ctx, next) => {
const ip = ctx.ip;
const now = Date.now();
if (!requests.has(ip)) {
requests.set(ip, { count: 0, resetTime: now + config.windowMs });
}
const requestData = requests.get(ip);
if (now > requestData.resetTime) {
requestData.count = 0;
requestData.resetTime = now + config.windowMs;
}
requestData.count++;
if (requestData.count > config.max) {
ctx.status = 429;
ctx.body = { error: config.message };
return;
}
await next();
};
};
// Apply middleware
app.use(corsMiddleware({ origin: 'http://localhost:3000' }));
app.use(rateLimitMiddleware({ max: 10, windowMs: 60000 })); // 10 requests per minute
app.use(authMiddleware());
// Routes
app.use(async (ctx) => {
ctx.body = {
message: 'Protected content',
user: ctx.state.user,
path: ctx.path,
method: ctx.method
};
});
// Public route
app.use(async (ctx, next) => {
if (ctx.path === '/public/info') {
ctx.body = { message: 'This is public information' };
return;
}
await next();
});
app.listen(3000);
console.log('Server with custom middleware running on http://localhost:3000');
// 5. Koa with Error Handling and Validation
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const { Joi } = require('koa-joi-router');
const app = new Koa();
const router = new Router();
// Custom error class
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
// Validation schema
const userSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150).optional()
});
// Error handling middleware
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
console.error('Error:', err);
if (err.isJoi) {
// Validation error
ctx.status = 400;
ctx.body = {
error: 'Validation Error',
details: err.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}))
};
} else if (err.isOperational) {
// Operational error
ctx.status = err.statusCode || 500;
ctx.body = { error: err.message };
} else {
// Programming error
ctx.status = 500;
ctx.body = { error: 'Internal Server Error' };
}
}
});
// Request validation middleware
const validate = (schema) => {
return async (ctx, next) => {
const { error, value } = schema.validate(ctx.request.body);
if (error) {
error.isJoi = true;
throw error;
}
ctx.request.validatedBody = value;
await next();
};
};
// Middleware
app.use(bodyParser());
// Routes with validation
router.post('/api/users', validate(userSchema), async (ctx) => {
const userData = ctx.request.validatedBody;
// Simulate database operation
const newUser = {
id: Date.now(),
...userData,
createdAt: new Date().toISOString()
};
ctx.status = 201;
ctx.body = {
message: 'User created successfully',
user: newUser
};
});
// Route that throws operational error
router.get('/api/error', async (ctx) => {
throw new AppError('This is an operational error', 400);
});
// Route that throws programming error
router.get('/api/crash', async (ctx) => {
throw new Error('Unexpected error occurred');
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
console.log('Server with error handling running on http://localhost:3000');
// 6. Koa with File Uploads and Static Files
const Koa = require('koa');
const Router = require('@koa/router');
const serve = require('koa-static');
const mount = require('koa-mount');
const formidable = require('koa-formidable');
const path = require('path');
const fs = require('fs').promises;
const app = new Koa();
const router = new Router();
// Serve static files
app.use(mount('/public', serve(path.join(__dirname, 'public'))));
// Ensure upload directory exists
const uploadDir = path.join(__dirname, 'uploads');
fs.mkdir(uploadDir, { recursive: true }).catch(console.error);
// File upload middleware
const uploadMiddleware = formidable({
uploadDir,
keepExtensions: true,
maxFileSize: 10 * 1024 * 1024, // 10MB
multiples: true
});
// Single file upload
router.post('/upload', uploadMiddleware, async (ctx) => {
try {
const files = ctx.request.files;
const file = files.file; // 'file' is the field name
if (!file) {
ctx.status = 400;
ctx.body = { error: 'No file uploaded' };
return;
}
ctx.body = {
message: 'File uploaded successfully',
file: {
name: file.name,
path: file.path,
size: file.size,
type: file.type
}
};
} catch (error) {
console.error('Upload error:', error);
ctx.status = 500;
ctx.body = { error: 'File upload failed' };
}
});
// Multiple files upload
router.post('/upload/multiple', uploadMiddleware, async (ctx) => {
try {
const files = ctx.request.files;
const uploadedFiles = [];
for (const [fieldName, fileArray] of Object.entries(files)) {
if (Array.isArray(fileArray)) {
fileArray.forEach(file => {
uploadedFiles.push({
field: fieldName,
name: file.name,
path: file.path,
size: file.size,
type: file.type
});
});
} else {
uploadedFiles.push({
field: fieldName,
name: fileArray.name,
path: fileArray.path,
size: fileArray.size,
type: fileArray.type
});
}
}
ctx.body = {
message: 'Files uploaded successfully',
files: uploadedFiles,
count: uploadedFiles.length
};
} catch (error) {
console.error('Multiple upload error:', error);
ctx.status = 500;
ctx.body = { error: 'Multiple file upload failed' };
}
});
// File download
router.get('/download/:filename', async (ctx) => {
try {
const filename = ctx.params.filename;
const filePath = path.join(uploadDir, filename);
const stats = await fs.stat(filePath);
ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
ctx.set('Content-Type', 'application/octet-stream');
ctx.body = await fs.readFile(filePath);
} catch (error) {
ctx.status = 404;
ctx.body = { error: 'File not found' };
}
});
// List uploaded files
router.get('/files', async (ctx) => {
try {
const files = await fs.readdir(uploadDir);
const fileInfos = [];
for (const file of files) {
const filePath = path.join(uploadDir, file);
const stats = await fs.stat(filePath);
fileInfos.push({
name: file,
size: stats.size,
createdAt: stats.birthtime.toISOString(),
downloadUrl: `/download/${file}`
});
}
ctx.body = {
files: fileInfos,
count: fileInfos.length
};
} catch (error) {
ctx.status = 500;
ctx.body = { error: 'Failed to list files' };
}
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
console.log('File server running on http://localhost:3000');
// 7. Koa with Session Management
const Koa = require('koa');
const Router = require('@koa/router');
const session = require('koa-session');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router();
// Session configuration
const sessionConfig = {
key: 'koa.sess', // cookie key
maxAge: 86400000, // 24 hours
autoCommit: true,
overwrite: true,
httpOnly: true,
signed: true,
rolling: false,
renew: false,
secure: false, // set to true in production with HTTPS
sameSite: null
};
app.keys = ['your-secret-key'];
// Middleware
app.use(session(sessionConfig, app));
app.use(bodyParser());
// Authentication routes
router.post('/login', async (ctx) => {
const { username, password } = ctx.request.body;
// Mock authentication (use proper password hashing in production)
if (username === 'admin' && password === 'password') {
ctx.session.user = {
id: 1,
username: 'admin',
role: 'admin'
};
ctx.body = {
message: 'Login successful',
user: ctx.session.user
};
} else {
ctx.status = 401;
ctx.body = { error: 'Invalid credentials' };
}
});
router.post('/logout', async (ctx) => {
ctx.session = null;
ctx.body = { message: 'Logged out successfully' };
});
router.get('/profile', async (ctx) => {
if (!ctx.session.user) {
ctx.status = 401;
ctx.body = { error: 'Not authenticated' };
return;
}
ctx.body = {
user: ctx.session.user,
sessionId: ctx.sessionId,
visits: (ctx.session.visits || 0) + 1
};
ctx.session.visits = (ctx.session.visits || 0) + 1;
});
// Shopping cart example
router.get('/cart', async (ctx) => {
const cart = ctx.session.cart || [];
ctx.body = { cart };
});
router.post('/cart/add', async (ctx) => {
const { productId, name, price } = ctx.request.body;
if (!ctx.session.cart) {
ctx.session.cart = [];
}
const existingItem = ctx.session.cart.find(item => item.productId === productId);
if (existingItem) {
existingItem.quantity += 1;
} else {
ctx.session.cart.push({
productId,
name,
price: parseFloat(price),
quantity: 1
});
}
ctx.body = {
message: 'Item added to cart',
cart: ctx.session.cart
};
});
router.delete('/cart/:productId', async (ctx) => {
const productId = parseInt(ctx.params.productId);
if (ctx.session.cart) {
ctx.session.cart = ctx.session.cart.filter(item => item.productId !== productId);
}
ctx.body = { message: 'Item removed from cart' };
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
console.log('Session-enabled server running on http://localhost:3000');
// 8. Koa with Database Integration (MongoDB)
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const mongoose = require('mongoose');
const app = new Koa();
const router = new Router();
// MongoDB connection
mongoose.connect('mongodb://localhost:27017/koa-app', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// User model
const UserSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: { type: Number, min: 0, max: 150 },
createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', UserSchema);
// Middleware
app.use(bodyParser());
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
}
});
// CRUD routes
router.post('/api/users', async (ctx) => {
try {
const user = new User(ctx.request.body);
await user.save();
ctx.status = 201;
ctx.body = user;
} catch (err) {
ctx.status = 400;
ctx.body = { error: err.message };
}
});
router.get('/api/users', async (ctx) => {
const users = await User.find();
ctx.body = { users, total: users.length };
});
router.get('/api/users/:id', async (ctx) => {
try {
const user = await User.findById(ctx.params.id);
if (!user) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
return;
}
ctx.body = user;
} catch (err) {
ctx.status = 400;
ctx.body = { error: 'Invalid user ID' };
}
});
router.put('/api/users/:id', async (ctx) => {
try {
const user = await User.findByIdAndUpdate(
ctx.params.id,
ctx.request.body,
{ new: true, runValidators: true }
);
if (!user) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
return;
}
ctx.body = user;
} catch (err) {
ctx.status = 400;
ctx.body = { error: err.message };
}
});
router.delete('/api/users/:id', async (ctx) => {
try {
const user = await User.findByIdAndDelete(ctx.params.id);
if (!user) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
return;
}
ctx.status = 204;
} catch (err) {
ctx.status = 400;
ctx.body = { error: 'Invalid user ID' };
}
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
console.log('Database-enabled server running on http://localhost:3000');
// 9. Koa with WebSocket Support
const Koa = require('koa');
const Router = require('@koa/router');
const WebSocket = require('ws');
const http = require('http');
const app = new Koa();
const router = new Router();
const server = http.createServer(app.callback());
// WebSocket server
const wss = new WebSocket.Server({ server });
// Store connected clients
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
console.log('Client connected, total clients:', clients.size);
// Send welcome message
ws.send(JSON.stringify({
type: 'welcome',
message: 'Connected to chat server',
timestamp: new Date().toISOString()
}));
// Handle incoming messages
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
// Broadcast message to all clients
const broadcast = {
type: 'message',
data: message,
timestamp: new Date().toISOString()
};
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(broadcast));
}
});
} catch (error) {
console.error('Invalid message format:', error);
}
});
// Handle client disconnection
ws.on('close', () => {
clients.delete(ws);
console.log('Client disconnected, total clients:', clients.size);
// Notify other clients
const notification = {
type: 'notification',
message: 'A user left the chat',
userCount: clients.size,
timestamp: new Date().toISOString()
};
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(notification));
}
});
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
clients.delete(ws);
});
});
// HTTP routes
router.get('/', async (ctx) => {
ctx.body = {
message: 'Koa WebSocket Server',
connectedClients: clients.size,
endpoints: [
'GET / - Server info',
'GET /clients - Connected clients count',
'WebSocket ws://localhost:3000 - WebSocket endpoint'
]
};
});
router.get('/clients', async (ctx) => {
ctx.body = {
connectedClients: clients.size,
timestamp: new Date().toISOString()
};
});
// Broadcast API
router.post('/broadcast', async (ctx) => {
const { message, type = 'notification' } = ctx.request.body;
const broadcast = {
type,
message,
timestamp: new Date().toISOString()
};
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(broadcast));
}
});
ctx.body = {
message: 'Broadcast sent to all clients',
clientCount: clients.size
};
});
app.use(router.routes());
app.use(router.allowedMethods());
// Start server
server.listen(3000, () => {
console.log('WebSocket server running on http://localhost:3000');
console.log('Connect with WebSocket client to ws://localhost:3000');
});
// 10. Koa Production Best Practices
const Koa = require('koa');
const Router = require('@koa/router');
const helmet = require('koa-helmet');
const compress = require('koa-compress');
const cors = require('@koa/cors');
const bodyParser = require('koa-bodyparser');
const rateLimit = require('koa-ratelimit');
const app = new Koa();
const router = new Router();
// Security middleware
app.use(helmet());
// Compression middleware
app.use(compress({
filter: (contentType) => {
return /text|javascript|json/i.test(contentType);
},
threshold: 1024,
gzip: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
deflate: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
br: false // disable brotli
}));
// CORS middleware
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
credentials: true
}));
// Rate limiting
app.use(rateLimit({
driver: 'memory',
db: new Map(),
duration: 60000, // 1 minute
max: 100, // 100 requests per minute
id: (ctx) => ctx.ip,
disableHeader: false,
whitelist: (ctx) => {
// Whitelist local development
return ctx.ip === '127.0.0.1' || ctx.ip === '::1';
},
blacklist: (ctx) => {
// Example: block specific IPs
return false;
}
}));
// Body parser with security options
app.use(bodyParser({
enableTypes: ['json', 'form'],
jsonLimit: '10mb',
formLimit: '10mb',
textLimit: '10mb',
strict: true, // only accept objects and arrays
detectJSON: (ctx) => {
return ctx.request.is('json');
}
}));
// Request logging
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${new Date().toISOString()} ${ctx.method} ${ctx.url} - ${ctx.status} - ${ms}ms`);
});
// Health check endpoint
router.get('/health', async (ctx) => {
ctx.body = {
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
environment: process.env.NODE_ENV || 'development'
};
});
// API versioning
router.get('/api/v1/info', async (ctx) => {
ctx.body = {
name: 'Koa Production API',
version: '1.0.0',
environment: process.env.NODE_ENV || 'development',
nodeVersion: process.version,
uptime: process.uptime()
};
});
// Graceful shutdown
const gracefulShutdown = (signal) => {
console.log(`
Received ${signal}. Starting graceful shutdown...`);
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
// Force close after 10 seconds
setTimeout(() => {
console.error('Could not close connections in time, forcefully shutting down');
process.exit(1);
}, 10000);
};
// Handle shutdown signals
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
gracefulShutdown('uncaughtException');
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
app.use(router.routes());
app.use(router.allowedMethods());
const server = app.listen(3000, () => {
console.log('🚀 Production-ready Koa server running on http://localhost:3000');
});