mcpField
E
expressjs-development

expressjs-development

github.com/manutej开发62557 次安装39 周前更新
代码仓库

关于 expressjs-development

expressjs-development 是面向 Express.js 开发者的开发技能指南,涵盖 Web 应用和 REST API 的实现。内容从路由与中间件延伸到错误处理、安全和部署实践。

主要功能

  1. 01Express.js 路由与中间件
  2. 02请求和响应处理
  3. 03错误处理模式
  4. 04身份验证与输入验证
  5. 05安全及性能实践

适用场景

  1. 01构建 REST API 和后端服务
  2. 02实现身份验证与授权
  3. 03配置文件上传和 Webhook 处理
  4. 04规划 Express.js 部署流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/manutej/luxor-claude-marketplace --skill expressjs-development

安装后在 Claude Code 中输入/expressjs-development确认已加载。

附带文件EXAMPLES.mdREADME.md

详细介绍

Express.js 开发技能

此技能提供使用 Express.js 构建可用于生产环境的 Web 应用和 REST API 的全面指导,涵盖路由、中间件、请求/响应处理、错误处理、身份验证、验证以及部署最佳实践。

何时使用此技能

在以下情况使用此技能:

  • 为 Web 和移动应用构建 RESTful API
  • 创建后端服务和微服务
  • 开发支持服务器端渲染的 Web 服务器
  • 实现 API 网关和代理服务器
  • 构建支持 WebSocket 的实时应用
  • 创建基于中间件的请求处理管道
  • 开发身份验证和授权系统
  • 实现文件上传和下载服务
  • 构建 Webhook 处理程序和集成
  • 使用 Express 创建无服务器函数

核心概念

应用设置

Express 应用通过创建 Express 实例并配置中间件和路由来构建。

基本 Express 应用:

const express = require('express');
const app = express();

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.get('/', (req, res) => {
  res.send('Hello World!');
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

带配置的应用:

const express = require('express');
const app = express();

// App settings
app.set('port', process.env.PORT || 3000);
app.set('env', process.env.NODE_ENV || 'development');
app.set('trust proxy', 1); // Trust first proxy

// View engine setup (optional)
app.set('view engine', 'ejs');
app.set('views', './views');

// Static files
app.use(express.static('public'));

// Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

module.exports = app;
路由

路由是指应用的端点(URI)如何响应客户端请求。

基本路由:

const express = require('express');
const app = express();

// HTTP Methods
app.get('/users', (req, res) => {
  res.json({ message: 'Get all users' });
});

app.post('/users', (req, res) => {
  res.json({ message: 'Create user' });
});

app.put('/users/:id', (req, res) => {
  res.json({ message: `Update user ${req.params.id}` });
});

app.delete('/users/:id', (req, res) => {
  res.json({ message: `Delete user ${req.params.id}` });
});

// Multiple methods on same route
app.route('/users/:id')
  .get((req, res) => res.json({ message: 'Get user' }))
  .put((req, res) => res.json({ message: 'Update user' }))
  .delete((req, res) => res.json({ message: 'Delete user' }));

路由参数:

// Single parameter
app.get('/users/:userId', (req, res) => {
  const { userId } = req.params;
  res.json({ userId });
});

// Multiple parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params;
  res.json({ userId, postId });
});

// Optional parameters with regex
app.get('/users/:userId/posts/:postId?', (req, res) => {
  // postId is optional
  res.json(req.params);
});

// Parameter validation
app.param('userId', (req, res, next, id) => {
  // Validate or transform parameter
  if (!id.match(/^\d+$/)) {
    return res.status(400).json({ error: 'Invalid user ID' });
  }
  req.userId = parseInt(id);
  next();
});

查询字符串:

// GET /search?q=express&limit=10&page=2
app.get('/search', (req, res) => {
  const { q, limit = 20, page = 1 } = req.query;
  res.json({
    query: q,
    limit: parseInt(limit),
    page: parseInt(page)
  });
});

路由器模块:

// routes/users.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.json({ message: 'Get all users' });
});

router.get('/:id', (req, res) => {
  res.json({ message: `Get user ${req.params.id}` });
});

router.post('/', (req, res) => {
  res.json({ message: 'Create user' });
});

module.exports = router;

// app.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);
中间件

中间件函数可以访问请求对象(req)、响应对象(res)以及应用请求-响应周期中的下一个中间件函数。

应用级中间件:

// Executed for every request
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// Executed for specific path
app.use('/api', (req, res, next) => {
  req.startTime = Date.now();
  next();
});

// Multiple middleware functions
app.use(
  express.json(),
  express.urlencoded({ extended: true }),
  cookieParser()
);

路由器级中间件:

const router = express.Router();

// Middleware for all routes in this router
router.use((req, res, next) => {
  console.log('Router middleware');
  next();
});

// Middleware for specific route
router.get('/users',
  authMiddleware,
  validationMiddleware,
  (req, res) => {
    res.json({ users: [] });
  }
);

内置中间件:

// Parse JSON bodies
app.use(express.json());

// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));

// Serve static files
app.use(express.static('public'));
app.use('/uploads', express.static('uploads'));

第三方中间件:

const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');

// Security headers
app.use(helmet());

// CORS
app.use(cors({
  origin: 'https://example.com',
  credentials: true
}));

// Logging
app.use(morgan('combined'));

// Compression
app.use(compression());

自定义中间件:

// Request logging middleware
function requestLogger(req, res, next) {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
  });

  next();
}

// Authentication middleware
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
}

// Request validation middleware
function validateUser(req, res, next) {
  const { email, password } = req.body;

  if (!email || !password) {
    return res.status(400).json({
      error: 'Email and password are required'
    });
  }

  if (!email.includes('@')) {
    return res.status(400).json({ error: 'Invalid email' });
  }

  next();
}

app.use(requestLogger);
app.post('/login', validateUser, loginHandler);
app.get('/protected', requireAuth, protectedHandler);
请求对象

请求对象表示 HTTP 请求,并包含查询字符串、参数、正文、标头等属性。

请求属性:

app.post('/api/users/:id', (req, res) => {
  // Route parameters
  const { id } = req.params;

  // Query string
  const { sort, filter } = req.query;

  // Request body
  const { name, email } = req.body;

  // Headers
  const userAgent = req.get('User-Agent');
  const contentType = req.get('Content-Type');

  // Request info
  const method = req.method;
  const path = req.path;
  const url = req.url;
  const baseUrl = req.baseUrl;
  const protocol = req.protocol;
  const hostname = req.hostname;
  const ip = req.ip;

  // Cookies (requires cookie-parser)
  const { sessionId } = req.cookies;

  res.json({ id, name, email });
});

请求方法:

app.post('/upload', (req, res) => {
  // Check content type
  if (req.is('application/json')) {
    // Handle JSON
  }

  // Check accept header
  if (req.accepts('json')) {
    res.json({ data: 'json response' });
  } else if (req.accepts('html')) {
    res.send('<html>html response</html>');
  }

  // Get header value
  const auth = req.get('Authorization');

  // Get range header
  const range = req.range(1000);
});
响应对象

响应对象表示 Express 应用收到 HTTP 请求时发送的 HTTP 响应。

发送响应:

app.get('/api/data', (req, res) => {
  // Send JSON
  res.json({ message: 'Success', data: [] });

  // Send string
  res.send('Hello World');

  // Send status
  res.sendStatus(200); // Equivalent to res.status(200).send('OK')

  // Send file
  res.sendFile('/path/to/file.pdf');

  // Download file
  res.download('/path/to/file.pdf', 'document.pdf');

  // Render view
  res.render('index', { title: 'Home' });

  // Redirect
  res.redirect('/login');
  res.redirect(301, 'https://example.com');

  // End response
  res.end();
});

设置状态和标头:

app.get('/api/resource', (req, res) => {
  // Set status code
  res.status(201).json({ created: true });

  // Set headers
  res.set('Content-Type', 'application/json');
  res.set({
    'X-API-Version': '1.0',
    'X-Rate-Limit': '100'
  });

  // Set cookie
  res.cookie('name', 'value', {
    maxAge: 900000,
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
  });

  // Clear cookie
  res.clearCookie('name');

  res.json({ success: true });
});

响应格式:

app.get('/api/users/:id', (req, res) => {
  const user = { id: 1, name: 'John' };

  res.format({
    'text/plain': () => {
      res.send(`${user.name}`);
    },
    'text/html': () => {
      res.send(`<p>${user.name}</p>`);
    },
    'application/json': () => {
      res.json(user);
    },
    default: () => {
      res.status(406).send('Not Acceptable');
    }
  });
});
错误处理

错误处理中间件函数有四个参数:(err、req、res、next)。

错误处理中间件:

// 404 handler
app.use((req, res, next) => {
  res.status(404).json({ error: 'Not found' });
});

// Error handler (must be last)
app.use((err, req, res, next) => {
  console.error(err.stack);

  res.status(err.status || 500).json({
    error: {
      message: err.message,
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
    }
  });
});

异步错误处理:

// Async wrapper utility
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Using async wrapper
app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);

  if (!user) {
    const error = new Error('User not found');
    error.status = 404;
    throw error;
  }

  res.json(user);
}));

// Custom error classes
class AppError extends Error {
  constructor(message, status) {
    super(message);
    this.status = status;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

class NotFoundError extends AppError {
  constructor(message = 'Resource not found') {
    super(message, 404);
  }
}

class ValidationError extends AppError {
  constructor(message = 'Validation failed') {
    super(message, 400);
  }
}

API 参考

Express 应用方法

app.use([path], middleware)

  • 在指定路径挂载中间件
  • 如果未指定路径,则每个请求都会执行该中间件

app.METHOD(path, [middleware...], handler)

  • 路由 HTTP 请求(GET、POST、PUT、DELETE 等)
  • 可以指定多个中间件函数

app.route(path)

  • 返回单个路由实例,以便链式调用 HTTP 动词

app.listen(port, [hostname], [backlog], [callback])

  • 在指定主机和端口上绑定并监听连接

app.param(name, callback)

  • 为路由参数添加回调触发器

app.set(name, value)

  • 将设置名称指定为相应的值

app.get(name)

  • 返回设置名称对应的值
路由器方法

router.use([path], middleware)

  • 为路由器挂载中间件

router.METHOD(path, [middleware...], handler)

  • 在路由器中路由 HTTP 请求

router.route(path)

  • 返回路由实例,以便链式调用

router.param(name, callback)

  • 添加参数回调
请求属性
  • req.body:包含解析后的请求正文(需要 body-parser)
  • req.params:路由参数
  • req.query:解析后的查询字符串
  • req.headers:请求标头
  • req.cookies:Cookie(需要 cookie-parser)
  • req.method:HTTP 方法
  • req.path:请求路径
  • req.url:完整 URL
  • req.ip:远程 IP 地址
  • req.protocol:请求协议(http 或 https)
请求方法
  • req.get(header):返回标头值
  • req.is(type):检查内容类型是否匹配
  • req.accepts(types):检查类型是否可接受
  • req.range(size):解析范围标头
响应方法
  • res.json(obj):发送 JSON 响应
  • res.send(body):发送响应
  • res.status(code):设置状态码
  • res.sendStatus(code):设置状态并发送状态消息
  • res.set(field, value):设置响应头
  • res.cookie(name, value, options):设置 cookie
  • res.clearCookie(name):清除 cookie
  • res.redirect([status], path):重定向到指定路径
  • res.render(view, locals):渲染视图模板
  • res.sendFile(path):发送文件
  • res.download(path, filename):下载文件

工作流模式

REST API 设计

完整的 REST API 示例:

const express = require('express');
const router = express.Router();

// GET /api/users - List all users
router.get('/', asyncHandler(async (req, res) => {
  const { page = 1, limit = 10, sort = 'createdAt' } = req.query;

  const users = await User.find()
    .sort(sort)
    .limit(parseInt(limit))
    .skip((parseInt(page) - 1) * parseInt(limit))
    .select('-password');

  const total = await User.countDocuments();

  res.json({
    data: users,
    pagination: {
      page: parseInt(page),
      limit: parseInt(limit),
      total,
      pages: Math.ceil(total / limit)
    }
  });
}));

// GET /api/users/:id - Get single user
router.get('/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id).select('-password');

  if (!user) {
    throw new NotFoundError('User not found');
  }

  res.json({ data: user });
}));

// POST /api/users - Create user
router.post('/',
  validateUser,
  asyncHandler(async (req, res) => {
    const { email, password, name } = req.body;

    const existingUser = await User.findOne({ email });
    if (existingUser) {
      throw new ValidationError('Email already exists');
    }

    const user = await User.create({ email, password, name });

    res.status(201).json({
      data: user.toJSON(),
      message: 'User created successfully'
    });
  })
);

// PUT /api/users/:id - Update user
router.put('/:id',
  requireAuth,
  validateUserUpdate,
  asyncHandler(async (req, res) => {
    const user = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true }
    ).select('-password');

    if (!user) {
      throw new NotFoundError('User not found');
    }

    res.json({
      data: user,
      message: 'User updated successfully'
    });
  })
);

// DELETE /api/users/:id - Delete user
router.delete('/:id',
  requireAuth,
  asyncHandler(async (req, res) => {
    const user = await User.findByIdAndDelete(req.params.id);

    if (!user) {
      throw new NotFoundError('User not found');
    }

    res.json({ message: 'User deleted successfully' });
  })
);

module.exports = router;
身份验证

JWT 身份验证:

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// Register
router.post('/register',
  validateRegistration,
  asyncHandler(async (req, res) => {
    const { email, password, name } = req.body;

    // Check if user exists
    const existingUser = await User.findOne({ email });
    if (existingUser) {
      throw new ValidationError('Email already registered');
    }

    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Create user
    const user = await User.create({
      email,
      password: hashedPassword,
      name
    });

    // Generate token
    const token = jwt.sign(
      { userId: user._id, email: user.email },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.status(201).json({
      data: {
        user: user.toJSON(),
        token
      }
    });
  })
);

// Login
router.post('/login',
  validateLogin,
  asyncHandler(async (req, res) => {
    const { email, password } = req.body;

    // Find user
    const user = await User.findOne({ email });
    if (!user) {
      throw new ValidationError('Invalid credentials');
    }

    // Verify password
    const isValid = await bcrypt.compare(password, user.password);
    if (!isValid) {
      throw new ValidationError('Invalid credentials');
    }

    // Generate token
    const token = jwt.sign(
      { userId: user._id, email: user.email },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.json({
      data: {
        user: user.toJSON(),
        token
      }
    });
  })
);

// Refresh token
router.post('/refresh',
  asyncHandler(async (req, res) => {
    const { refreshToken } = req.body;

    if (!refreshToken) {
      throw new ValidationError('Refresh token required');
    }

    const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);

    const token = jwt.sign(
      { userId: decoded.userId, email: decoded.email },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.json({ data: { token } });
  })
);

// Auth middleware
function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    throw new AuthenticationError('No token provided');
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    throw new AuthenticationError('Invalid token');
  }
}

// Role-based authorization
function requireRole(...roles) {
  return async (req, res, next) => {
    const user = await User.findById(req.user.userId);

    if (!user || !roles.includes(user.role)) {
      throw new ForbiddenError('Insufficient permissions');
    }

    next();
  };
}
验证

使用 express-validator 验证输入:

const { body, param, query, validationResult } = require('express-validator');

// Validation middleware
const validate = (validations) => {
  return async (req, res, next) => {
    await Promise.all(validations.map(validation => validation.run(req)));

    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        error: 'Validation failed',
        details: errors.array()
      });
    }

    next();
  };
};

// User validation rules
const userValidationRules = {
  create: validate([
    body('email')
      .isEmail()
      .normalizeEmail()
      .withMessage('Invalid email address'),
    body('password')
      .isLength({ min: 8 })
      .withMessage('Password must be at least 8 characters')
      .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
      .withMessage('Password must contain uppercase, lowercase, and number'),
    body('name')
      .trim()
      .isLength({ min: 2, max: 50 })
      .withMessage('Name must be between 2 and 50 characters')
  ]),

  update: validate([
    param('id')
      .isMongoId()
      .withMessage('Invalid user ID'),
    body('email')
      .optional()
      .isEmail()
      .normalizeEmail(),
    body('name')
      .optional()
      .trim()
      .isLength({ min: 2, max: 50 })
  ]),

  list: validate([
    query('page')
      .optional()
      .isInt({ min: 1 })
      .toInt(),
    query('limit')
      .optional()
      .isInt({ min: 1, max: 100 })
      .toInt()
  ])
};

// Using validation
router.post('/users', userValidationRules.create, createUser);
router.put('/users/:id', userValidationRules.update, updateUser);
router.get('/users', userValidationRules.list, listUsers);
数据库集成

使用 Mongoose 操作 MongoDB:

const mongoose = require('mongoose');

// Connect to database
async function connectDB() {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log('MongoDB connected');
  } catch (error) {
    console.error('MongoDB connection error:', error);
    process.exit(1);
  }
}

// User model
const userSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true
  },
  password: {
    type: String,
    required: true
  },
  name: {
    type: String,
    required: true
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  }
}, {
  timestamps: true
});

userSchema.methods.toJSON = function() {
  const user = this.toObject();
  delete user.password;
  return user;
};

const User = mongoose.model('User', userSchema);

// CRUD operations
router.get('/users', asyncHandler(async (req, res) => {
  const users = await User.find().select('-password');
  res.json({ data: users });
}));

router.post('/users', asyncHandler(async (req, res) => {
  const user = await User.create(req.body);
  res.status(201).json({ data: user });
}));

router.put('/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findByIdAndUpdate(
    req.params.id,
    req.body,
    { new: true, runValidators: true }
  );
  res.json({ data: user });
}));

router.delete('/users/:id', asyncHandler(async (req, res) => {
  await User.findByIdAndDelete(req.params.id);
  res.json({ message: 'User deleted' });
}));
测试

使用 Jest 和 Supertest 测试 API:

const request = require('supertest');
const app = require('../app');
const User = require('../models/User');

describe('User API', () => {
  beforeEach(async () => {
    await User.deleteMany({});
  });

  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const userData = {
        email: '[email protected]',
        password: 'Password123',
        name: 'Test User'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(201);

      expect(response.body.data).toHaveProperty('email', userData.email);
      expect(response.body.data).not.toHaveProperty('password');
    });

    it('should return 400 for invalid email', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'invalid-email',
          password: 'Password123',
          name: 'Test'
        })
        .expect(400);

      expect(response.body).toHaveProperty('error');
    });
  });

  describe('GET /api/users/:id', () => {
    it('should return user by id', async () => {
      const user = await User.create({
        email: '[email protected]',
        password: 'hashed',
        name: 'Test User'
      });

      const response = await request(app)
        .get(`/api/users/${user._id}`)
        .expect(200);

      expect(response.body.data).toHaveProperty('email', user.email);
    });

    it('should return 404 for non-existent user', async () => {
      const response = await request(app)
        .get('/api/users/507f1f77bcf86cd799439011')
        .expect(404);

      expect(response.body).toHaveProperty('error');
    });
  });

  describe('Authentication', () => {
    it('should require authentication for protected routes', async () => {
      await request(app)
        .get('/api/protected')
        .expect(401);
    });

    it('should allow access with valid token', async () => {
      const token = jwt.sign({ userId: '123' }, process.env.JWT_SECRET);

      await request(app)
        .get('/api/protected')
        .set('Authorization', `Bearer ${token}`)
        .expect(200);
    });
  });
});

最佳实践

安全

使用 Helmet 设置安全标头:

const helmet = require('helmet');

// Use helmet for security headers
app.use(helmet());

// Custom configuration
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", 'data:', 'https:']
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

CORS 配置:

const cors = require('cors');

// Allow all origins (development only)
app.use(cors());

// Production configuration
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || 'https://example.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400 // 24 hours
}));

// Dynamic origin validation
app.use(cors({
  origin: (origin, callback) => {
    const allowedOrigins = ['https://example.com', 'https://app.example.com'];

    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

速率限制:

const rateLimit = require('express-rate-limit');

// General API rate limiter
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP',
  standardHeaders: true,
  legacyHeaders: false
});

app.use('/api/', apiLimiter);

// Strict rate limiter for authentication
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true
});

app.use('/api/login', authLimiter);
app.use('/api/register', authLimiter);

// Custom key generator
const customLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 100,
  keyGenerator: (req) => {
    return req.user?.id || req.ip;
  }
});

输入清理:

const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');

// Prevent NoSQL injection
app.use(mongoSanitize());

// Prevent XSS attacks
app.use(xss());

// Custom sanitization middleware
function sanitizeInput(req, res, next) {
  if (req.body) {
    Object.keys(req.body).forEach(key => {
      if (typeof req.body[key] === 'string') {
        req.body[key] = req.body[key].trim();
      }
    });
  }
  next();
}

app.use(sanitizeInput);
性能

响应压缩:

const compression = require('compression');

// Enable compression
app.use(compression({
  level: 6,
  threshold: 1024,
  filter: (req, res) => {
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  }
}));

缓存:

// Simple in-memory cache
const cache = new Map();

function cacheMiddleware(duration) {
  return (req, res, next) => {
    const key = req.originalUrl;
    const cached = cache.get(key);

    if (cached && Date.now() < cached.expiry) {
      return res.json(cached.data);
    }

    res.originalJson = res.json;
    res.json = (data) => {
      cache.set(key, {
        data,
        expiry: Date.now() + duration * 1000
      });
      res.originalJson(data);
    };

    next();
  };
}

// Use cache
app.get('/api/users', cacheMiddleware(60), getUsers);

// Redis cache
const redis = require('redis');
const client = redis.createClient();

async function redisCache(duration) {
  return async (req, res, next) => {
    const key = `cache:${req.originalUrl}`;

    const cached = await client.get(key);
    if (cached) {
      return res.json(JSON.parse(cached));
    }

    res.originalJson = res.json;
    res.json = async (data) => {
      await client.setEx(key, duration, JSON.stringify(data));
      res.originalJson(data);
    };

    next();
  };
}

请求超时:

function timeout(ms) {
  return (req, res, next) => {
    req.setTimeout(ms, () => {
      res.status(408).json({ error: 'Request timeout' });
    });
    next();
  };
}

app.use(timeout(30000)); // 30 seconds
错误处理

集中式错误处理:

// Custom error classes
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

class ValidationError extends AppError {
  constructor(message) {
    super(message, 400);
  }
}

class AuthenticationError extends AppError {
  constructor(message) {
    super(message, 401);
  }
}

class NotFoundError extends AppError {
  constructor(message) {
    super(message, 404);
  }
}

// Error handler
function errorHandler(err, req, res, next) {
  let error = { ...err };
  error.message = err.message;

  // Log error
  console.error(err);

  // Mongoose validation error
  if (err.name === 'ValidationError') {
    const message = Object.values(err.errors).map(e => e.message).join(', ');
    error = new ValidationError(message);
  }

  // Mongoose duplicate key
  if (err.code === 11000) {
    const field = Object.keys(err.keyValue)[0];
    error = new ValidationError(`${field} already exists`);
  }

  // JWT errors
  if (err.name === 'JsonWebTokenError') {
    error = new AuthenticationError('Invalid token');
  }

  if (err.name === 'TokenExpiredError') {
    error = new AuthenticationError('Token expired');
  }

  res.status(error.statusCode || 500).json({
    error: {
      message: error.message || 'Server error',
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
    }
  });
}

app.use(errorHandler);
日志记录

Morgan 和 Winston:

const morgan = require('morgan');
const winston = require('winston');

// Winston logger
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

// Morgan HTTP logging
app.use(morgan('combined', {
  stream: {
    write: (message) => logger.info(message.trim())
  }
}));

// Custom logging middleware
app.use((req, res, next) => {
  logger.info({
    method: req.method,
    url: req.url,
    ip: req.ip,
    userAgent: req.get('user-agent')
  });
  next();
});
API 版本控制

URL 版本控制:

// Version 1 routes
const v1Router = express.Router();
v1Router.get('/users', getUsersV1);
app.use('/api/v1', v1Router);

// Version 2 routes
const v2Router = express.Router();
v2Router.get('/users', getUsersV2);
app.use('/api/v2', v2Router);

标头版本控制:

function apiVersion(version) {
  return (req, res, next) => {
    const requestedVersion = req.get('API-Version') || '1.0';

    if (requestedVersion === version) {
      next();
    } else {
      next('route');
    }
  };
}

app.get('/api/users', apiVersion('1.0'), getUsersV1);
app.get('/api/users', apiVersion('2.0'), getUsersV2);

示例

1. 基础 Express 服务器
const express = require('express');
const app = express();

app.use(express.json());

app.get('/', (req, res) => {
  res.json({ message: 'Hello Express!' });
});

app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    timestamp: new Date().toISOString()
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
2. 完整的 REST API
const express = require('express');
const mongoose = require('mongoose');
const app = express();

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Models
const Product = mongoose.model('Product', {
  name: { type: String, required: true },
  price: { type: Number, required: true },
  description: String,
  inStock: { type: Boolean, default: true }
});

// Routes
app.get('/api/products', async (req, res, next) => {
  try {
    const products = await Product.find();
    res.json({ data: products });
  } catch (error) {
    next(error);
  }
});

app.get('/api/products/:id', async (req, res, next) => {
  try {
    const product = await Product.findById(req.params.id);
    if (!product) {
      return res.status(404).json({ error: 'Product not found' });
    }
    res.json({ data: product });
  } catch (error) {
    next(error);
  }
});

app.post('/api/products', async (req, res, next) => {
  try {
    const product = await Product.create(req.body);
    res.status(201).json({ data: product });
  } catch (error) {
    next(error);
  }
});

app.put('/api/products/:id', async (req, res, next) => {
  try {
    const product = await Product.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true }
    );
    if (!product) {
      return res.status(404).json({ error: 'Product not found' });
    }
    res.json({ data: product });
  } catch (error) {
    next(error);
  }
});

app.delete('/api/products/:id', async (req, res, next) => {
  try {
    const product = await Product.findByIdAndDelete(req.params.id);
    if (!product) {
      return res.status(404).json({ error: 'Product not found' });
    }
    res.json({ message: 'Product deleted' });
  } catch (error) {
    next(error);
  }
});

// Error handler
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: err.message });
});

// Start server
mongoose.connect('mongodb://localhost/shop')
  .then(() => {
    app.listen(3000, () => console.log('Server running'));
  });
3. 身份验证系统
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const app = express();

app.use(express.json());

const users = new Map(); // In-memory storage

// Register
app.post('/api/register', async (req, res) => {
  const { email, password, name } = req.body;

  if (users.has(email)) {
    return res.status(400).json({ error: 'Email already exists' });
  }

  const hashedPassword = await bcrypt.hash(password, 10);

  users.set(email, {
    email,
    password: hashedPassword,
    name,
    id: Date.now().toString()
  });

  res.status(201).json({ message: 'User created' });
});

// Login
app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;

  const user = users.get(email);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const isValid = await bcrypt.compare(password, user.password);
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const token = jwt.sign(
    { userId: user.id, email: user.email },
    'secret-key',
    { expiresIn: '24h' }
  );

  res.json({ token });
});

// Protected route
app.get('/api/profile', (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token' });
  }

  try {
    const decoded = jwt.verify(token, 'secret-key');
    const user = Array.from(users.values()).find(u => u.id === decoded.userId);

    res.json({
      email: user.email,
      name: user.name
    });
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
});

app.listen(3000);

请参阅 EXAMPLES.md,了解 15 个以上的其他示例,涵盖文件上传、CORS、速率限制、WebSockets、测试、部署等内容。

总结

这项 Express.js 开发技能涵盖:

  1. 核心概念:应用设置、路由、中间件、请求/响应处理、错误处理
  2. API 参考:Express 方法和属性的完整参考
  3. 工作流模式:REST API 设计、身份验证、验证、数据库集成、测试
  4. 最佳实践:安全(helmet、CORS、速率限制)、性能(压缩、缓存)、错误处理、日志记录、API 版本控制
  5. 实际示例:针对常见用例的完整实现

这些模式和示例基于 Express.js 最佳实践(信任评分:9),代表了现代 Node.js 后端开发标准。

评论

支持 Markdown