快速入门 Express 框架

283 阅读1分钟

一、Express 适合做什么

  • 传统web网站
  • API 接口服务器
  • 服务端渲染中间层
  • 开发辅助工具
  • 自定义集成框架

二、Express 安装与使用

  • 可以在当前项目中安装
npm install express
  • 快速创建一个应用的骨架
npx express-generator
  • 简单使用
const express = require('express');
const app = express();

// 中间件 解析 JSON 请求体
app.use(express.json());

// 处理 GET 请求
app.get('/user/:id', (req, res) => {
  res.send(`用户ID: ${req.params.id}`);
});

// 处理 POST 请求
app.post('/login', express.json(), (req, res) => {
  const { username, password } = req.body;
  res.json({ status: 'success', user: username });
});

// 监听3000端口服务
app.listen(3000,()=>{
    console.log("run http://127.0.0.1:3000")
})

三、Express 路由

1、 路由的方法

方法描述示例
app.get()处理 GET 请求获取资源
app.post()处理 POST 请求创建资源
app.put()处理 PUT 请求更新整个资源
app.patch()处理 PATCH 请求部分更新资源
app.delete()处理 DELETE 请求删除资源
app.all()处理所有 HTTP 方法请求通用处理
const express = require('express');
const app = express();

// GET 方法示例
app.get('/api/users', (req, res) => {
  res.send('获取所有用户');
});

// POST 方法示例
app.post('/api/users', (req, res) => {
  res.status(201).send('创建新用户');
});

// PUT 方法示例
app.put('/api/users/:id', (req, res) => {
  res.send(`更新用户 ${req.params.id}`);
});

// DELETE 方法示例
app.delete('/api/users/:id', (req, res) => {
  res.send(`删除用户 ${req.params.id}`);
});

// PATCH 方法示例
app.patch('/api/users/:id', (req, res) => {
  res.send(`部分更新用户 ${req.params.id}`);
});

// ALL 方法示例 - 处理所有方法
app.all('/api/status', (req, res) => {
  res.send(`通过 ${req.method} 方法访问状态页面`);
});

2、路由参数

  • 基本路由参数
app.get('/users/:userId', (req, res) => {
  res.send(`用户ID: ${req.params.userId}`);
});
  • 多个路由参数
app.get('/posts/:year/:month/:day', (req, res) => {
  const { year, month, day } = req.params;
  res.send(`发布日期: ${year}${month}${day}日`);
});
  • 可选路由参数
app.get('/books/:category?', (req, res) => {
  if (req.params.category) {
    res.send(`分类: ${req.params.category}`);
  } else {
    res.send('所有书籍');
  }
});
  • 通配符路由参数
app.get('/files/*', (req, res) => {
  const filePath = req.params[0]; // 获取通配符匹配的部分
  res.send(`文件路径: ${filePath}`);
});
  • 正则表达式路由参数
// 只匹配数字ID
app.get('/articles/:id(\\d+)', (req, res) => {
  res.send(`文章ID: ${req.params.id} (数字)`);
});

// 匹配特定格式
app.get('/date/:year(\\d{4})-:month(\\d{2})-:day(\\d{2})', (req, res) => {
  res.send(`日期: ${req.params.year}-${req.params.month}-${req.params.day}`);
});
  • 使用中间件处理通用参数

// 验证用户ID的中间件
const validateUserId = (req, res, next) => {
  const userId = parseInt(req.params.userId);
  
  if (isNaN(userId) || userId <= 0) {
    return res.status(400).json({ error: '无效的用户ID' });
  }
  
  req.validatedUserId = userId;
  next();
};

// 应用中间件
app.get('/users/:userId/profile', validateUserId, (req, res) => {
  // 使用验证后的ID
  const userId = req.validatedUserId;
  // 获取用户资料...
});
  • 路由参数预处理

app.param('userId', (req, res, next, userId) => {
  // 从数据库获取用户
  User.findById(userId, (err, user) => {
    if (err) return next(err);
    if (!user) return res.status(404).send('用户未找到');
    
    req.user = user;
    next();
  });
});

app.get('/users/:userId', (req, res) => {
  res.json(req.user);
});
  • 路由参数 vs 查询参数

特性路由参数查询参数
位置URL路径中URL ? 后
访问req.paramsreq.query
示例/users/123/users?id=123
用途标识资源过滤、排序等
可见性公开公开
SEO影响较高较低
  • 路由参数与RESTful API

RESTful API

// RESTful 资源路由
app.get('/api/users', (req, res) => {
  // 获取所有用户
});

app.post('/api/users', (req, res) => {
  // 创建新用户
});

app.get('/api/users/:id', (req, res) => {
  // 获取特定用户
});

app.put('/api/users/:id', (req, res) => {
  // 更新用户
});

app.delete('/api/users/:id', (req, res) => {
  // 删除用户
});

3、路由模块化

使用该类express.Router创建模块化、可挂载的路由处理程序

以下示例创建一个路由器作为模块,在其中加载一个中间件功能,定义一些路由,并将路由器模块挂载到主应用程序中的路径上。

app目录下创建一个名为router的文件users.js,内容如下:

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

// 用户列表
router.get('/', (req, res) => {
  res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
});

// 用户详情
router.get('/details:id', (req, res) => {
  res.json({ id: req.params.id, name: 'User' + req.params.id });
});

module.exports = router;
const userRouter = require('./routes/users');
app.use('/users', userRouter);

该应用程序现在将能够处理对/users和的请求/users/details/12

四、Express 响应处理

方法描述
res.download()提示要下载的文件。
res.end()结束响应过程。
res.json()发送 JSON 响应。
res.jsonp()发送具有 JSONP 支持的 JSON 响应。
res.redirect()重定向请求。
res.render()渲染视图模板。
res.send()发送各种类型的回应。
res.sendFile()以八位字节流的形式发送文件。
res.sendStatus()设置响应状态代码并将其字符串表示形式作为响应主体发送。
  • res.send([body]) - 发送各种类型的数据,自动设置正确的 Content-Type 头
// 发送文本
res.send('Hello World!');

// 发送 HTML
res.send('<h1>Express 响应</h1>');

// 发送 JSON 对象(自动转换为 JSON 字符串)
res.send({ message: 'Success', code: 200 });

// 发送数组
res.send([1, 2, 3]);

// 发送 Buffer(自动设置 Content-Type)
res.send(Buffer.from('some data'));
  • res.json([body]) - 专门用于发送 JSON 响应
res.json({ 
  success: true,
  data: { id: 1, name: 'Alice' }
});

// 设置状态码 + JSON
res.status(201).json({ id: 2, name: 'Bob' });
  • res.status() - 设置 HTTP 状态码(通常与其他响应方法链式调用)
res.status(404).send('Page not found');
res.status(500).json({ error: 'Internal server error' });
res.status(201).send('Resource created');
  • res.sendStatus() - 状态码 + 默认消息
res.sendStatus(200); // 等同于 res.status(200).send('OK')
res.sendStatus(404); // 等同于 res.status(404).send('Not Found')
res.sendStatus(500); // 等同于 res.status(500).send('Internal Server Error')
  • res.redirect([status,] path) - 将客户端重定向到新 URL:
// 相对路径重定向
res.redirect('/new-location');

// 绝对路径重定向
res.redirect('https://example.com');

// 带状态码的重定向(默认为 302)
res.redirect(301, '/permanent-redirect');

// 回退重定向
res.redirect('back'); // 重定向到 Referer 头中的地址
  • res.render(view [, locals] [, callback]) - 渲染view并将渲染后的 HTML 字符串发送到客户端
// send the rendered view to the client
res.render('index')

// if a callback is specified, the rendered HTML string has to be sent explicitly
res.render('index', function (err, html) {
  res.send(html)
})

// pass a local variable to the view
res.render('user', { name: 'Tobi' }, function (err, html) {
  // ...
})
  • res.end([data] [, encoding]) - 结束响应过程
res.end()
res.status(404).end()
  • res.sendFile(path [, options] [, fn]) - 发送文件
const path = require('path');

// 发送文件(自动设置 Content-Type)
res.sendFile('/path/to/file.pdf');

// 设置文件名(下载时使用)
res.sendFile('/path/to/report.pdf', {
  root: process.cwd(),
  headers: {
    'Content-Disposition': 'attachment; filename="monthly-report.pdf"'
  }
});
  • res.download(path [, filename] [, options] [, fn]) - 文件下载
// 基本下载
res.download('/path/to/file.zip');

// 设置下载文件名
res.download('/path/to/document.pdf', 'report.pdf');

// 错误处理回调
res.download('/path/to/file', 'filename.ext', (err) => {
  if (err) {
    // 处理错误(如文件不存在)
  } else {
    console.log('文件已发送');
  }
});
  • res.set(field [, value]) 设置响应头
// 设置单个响应头
res.set('Content-Type', 'text/html');

// 设置多个响应头
res.set({
  'Content-Type': 'application/json',
  'Cache-Control': 'no-cache',
  'X-Custom-Header': 'value'
});
  • ### res.type(type) 设置响应头中 Content-Type
// 设置 Content-Type 快捷方式
res.type('html');       // => 'text/html'
res.type('json');       // => 'application/json'
res.type('png');        // => 'image/png'
res.type('application/xml'); // 完整类型
  • res.jsonp([body]) - 发送支持 JSONP 的 JSON 响应。此方法与 相同res.json(),只是它支持 JSONP 回调。
res.jsonp(null)
// => callback(null)

res.jsonp({ user: 'tobi' })
// => callback({ "user": "tobi" })

res.status(500).jsonp({ error: 'message' })
// => callback({ "error": "message" })

五、Express 中间件

  • Express 中间件,是在请求和响应之间执行的一系列函数。每个中间件函数都可以访问请求对象req、响应对象resnext 函数(next用于调用下一个中间件)。

  • 中间件类型

    1、 应用层中间件:绑定到 app 对象

    2、 路由层中间件:绑定到 router 对象

    3、 错误处理中间件:处理错误(4个参数)

    4、 内置中间件:Express 自带

    5、 第三方中间件:社区提供

中间件使用(应用和路由)

  • 全局中间件
const express = require('express');
const app = express();

// 注册全局中间件(所有请求都会执行)
app.use((req, res, next) => {
  console.log('全局中间件 - 每个请求都会执行');
  next();
});
  • 特定路径中间件
// 只对 /api 开头的路径生效
app.use('/api', (req, res, next) => {
  console.log('API 请求中间件');
  next();
});
  • 特定路径和请求方式中间件
// 只对 GET /users 请求生效
app.get('/users', (req, res, next) => {
  console.log('用户路由中间件');
  next();
}, (req, res) => {
  res.send('用户列表');
});
  • 多个中间件
const logRequest = (req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
};

const authCheck = (req, res, next) => {
  if (req.headers.authorization) {
    next();
  } else {
    res.status(401).send('未授权');
  }
};

// 组合使用多个中间件
app.get('/secure', logRequest, authCheck, (req, res) => {
  res.send('安全内容');
});

常用内置中间件

  • express.json() - 解析 JSON 请求体
app.use(express.json()); // 解析 application/json

// 使用
app.post('/users', (req, res) => {
  console.log(req.body); // 访问解析后的JSON数据
  res.json(req.body);
});
  • express.urlencoded() - 解析 URL 编码的请求体
app.use(express.urlencoded({ extended: true })); // 解析 application/x-www-form-urlencoded

// 使用
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 处理登录逻辑
});
  • express.static() - 托管静态文件
app.use(express.static('public')); // 托管 public 目录

// 访问方式:http://localhost:3000/images/logo.png
  • exoress.Router() - 创建模块化路由
const router = express.Router();

// 路由特定中间件
router.use((req, res, next) => {
  console.log('路由中间件');
  next();
});

router.get('/', (req, res) => {
  res.send('用户主页');
});

app.use('/users', router); // 挂载路由

常用第三方中间件

  • morgan - HTTP 请求日志
npm install helmet
const helmet = require('helmet');
app.use(helmet()); // 设置安全相关的 HTTP 头
  • helmet - 安全头部
npm install helmet
const helmet = require('helmet');
app.use(helmet()); // 设置安全相关的 HTTP 头
  • cors - 跨域支持
npm install cors
const cors = require('cors');
app.use(cors()); // 启用 CORS
  • cookie-parser - Cookie 解析
npm install cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser()); // 解析 Cookie
  • compression -响应压缩
npm install compression
const compression = require('compression');
app.use(compression()); // 压缩响应数据

自定义中间件开发

  • 请求日志中间件
const requestLogger = (req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} [${duration}ms]`);
  });
  
  next();
};

app.use(requestLogger);
  • 认证中间件
const authenticate = (req, res, next) => {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: '未提供认证令牌' });
  }
  
  const token = authHeader.split(' ')[1];
  
  // 验证 token (示例)
  if (token === 'valid-token') {
    req.user = { id: 1, name: 'John Doe' }; // 附加用户信息到请求对象
    next();
  } else {
    res.status(403).json({ error: '无效令牌' });
  }
};

// 使用
app.get('/profile', authenticate, (req, res) => {
  res.json({ user: req.user });
});
  • API 响应格式化中间件
app.use((req, res, next) => {
  // 成功响应方法
  res.apiSuccess = (data, status = 200) => {
    res.status(status).json({
      success: true,
      data
    });
  };
  
  // 错误响应方法
  res.apiError = (message, status = 400) => {
    res.status(status).json({
      success: false,
      error: message
    });
  };
  
  next();
});

// 使用
app.get('/products', (req, res) => {
  try {
    const products = getProducts(); // 获取产品
    res.apiSuccess(products);
  } catch (error) {
    res.apiError(error.message, 500);
  }
});

错误处理中间件

  • 基本结构
// 错误处理中间件必须有4个参数
app.use((err, req, res, next) => {
  console.error(err.stack);
  
  const status = err.statusCode || 500;
  const message = process.env.NODE_ENV === 'production' 
    ? '服务器错误' 
    : err.message;
  
  res.status(status).json({
    error: {
      status,
      message
    }
  });
});
  • 增强版错误处理
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

// 全局错误处理中间件
app.use((err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  
  if (process.env.NODE_ENV === 'development') {
    res.status(err.statusCode).json({
      status: 'error',
      message: err.message,
      stack: err.stack,
      error: err
    });
  } else {
    // 生产环境:不泄露堆栈信息
    res.status(err.statusCode).json({
      status: 'error',
      message: err.isOperational ? err.message : '发生意外错误'
    });
  }
});

// 在路由中使用
app.get('/error', (req, res, next) => {
  // 抛出可操作错误
  next(new AppError('测试错误', 400));
  
  // 抛出不可操作错误
  // throw new Error('未处理错误');
});

中间件例子

  • 中间件执行顺序
// 1. 安全相关中间件
app.use(helmet());
app.use(cors());

// 2. 请求解析中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// 3. 自定义中间件
app.use(requestLogger);
app.use(authenticate);

// 4. 路由
app.use('/api/v1', apiRouter);

// 5. 404 处理
app.use((req, res, next) => {
  res.status(404).json({
    status: 'fail',
    message: `未找到 ${req.originalUrl}`
  });
});

// 6. 全局错误处理(必须放在最后)
app.use(globalErrorHandler);
  • 可配置中间件
// 可配置的缓存中间件
const cacheMiddleware = (duration) => {
  return (req, res, next) => {
    res.set('Cache-Control', `public, max-age=${duration}`);
    next();
  };
};

// 使用
app.get('/static-data', cacheMiddleware(3600), (req, res) => {
  res.json({ data: '缓存数据' });
});

中间件执行流程图

graph TD
    A[客户端请求] --> B[安全中间件 helmet, cors]
    B --> C[请求解析中间件 json, urlencoded]
    C --> D[自定义中间件 logger, auth]
    D --> E[路由处理]
    E --> F{是否调用 next error?}
    F -->|是| G[错误处理中间件]
    F -->|否| H[发送响应]
    G --> I[发送错误响应]
    H --> J[结束]
    I --> J