前后端分离架构下,API就是前后端之间唯一的契约。一个设计良好的API能让前端开发顺滑、后端维护轻松;反之,一个设计糟糕的API会让每一行对接代码都充满踩雷。本文系统讲解前后端API的设计规范、鉴权方案、参数校验、错误处理、版本管理与实战落地,并给出Node后端 + Vue前端的可运行示例。
一、什么是API接口
API(Application Programming Interface,应用程序编程接口)是一组定义好的协议,让不同程序之间可以通信。Web开发中的"前后端API接口"特指:前端通过HTTP协议向后端发起请求,后端按约定的数据格式(多为JSON)返回结果。
1. 一个API的完整组成
- URL:接口地址,标识资源位置
- Method:HTTP方法(GET/POST/PUT/DELETE…)
- 请求参数:Query、Path、Body、Header
- 响应数据:状态码 + JSON数据
- 鉴权:Token/Cookie/API Key
二、RESTful设计规范
REST(Representational State Transfer)是当前最主流的API设计风格,核心思想是:把一切视为资源,用HTTP方法表示操作。
1. 资源命名
- 用名词复数:
/users而非/user - 层级表达从属:
/users/123/orders - 避免动词:
/users/123而非/getUser?id=123
2. HTTP方法语义
| 方法 | 语义 | 幂等 | 安全 |
|---|---|---|---|
| GET | 查询资源 | 是 | 是 |
| POST | 创建资源 | 否 | 否 |
| PUT | 整体替换 | 是 | 否 |
| PATCH | 局部更新 | 否 | 否 |
| DELETE | 删除资源 | 是 | 否 |
幂等:多次执行结果相同。安全:不修改服务端状态。
3. 典型CRUD接口
| 操作 | 方法 | URL |
|---|---|---|
| 列表 | GET | /api/users |
| 详情 | GET | /api/users/:id |
| 创建 | POST | /api/users |
| 更新 | PUT | /api/users/:id |
| 删除 | DELETE | /api/users/:id |
三、状态码体系
1. 常见状态码
- 2xx 成功:200 OK、201 Created、204 No Content
- 3xx 重定向:301/302/304
- 4xx 客户端错误:400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、422 Unprocessable Entity、429 Too Many Requests
- 5xx 服务端错误:500 Internal Server Error、502 Bad Gateway、503 Service Unavailable
2. 推荐使用策略
| 场景 | 状态码 |
|---|---|
| 查询成功 | 200 |
| 创建成功 | 201 |
| 删除成功(无返回体) | 204 |
| 参数错误 | 400 / 422 |
| 未登录 | 401 |
| 无权限 | 403 |
| 资源不存在 | 404 |
| 服务异常 | 500 |
四、统一响应格式
无论成功还是失败,都应使用统一的JSON结构,方便前端统一处理:
// 成功
{
"code": 0,
"message": "ok",
"data": { "id": 1, "name": "信仰" }
}
// 失败
{
"code": 40001,
"message": "参数错误:email格式不正确",
"data": null
}
设计要点:
code:业务状态码,0表示成功,其余为业务错误码message:人类可读的提示信息data:业务数据,失败时为null- 分页数据单独约定:
{ list, total, page, pageSize }
五、鉴权方案
1. Session + Cookie
传统Web网站常用,服务端保存Session,前端通过Cookie自动携带SessionID。适合同源Web场景,跨域/移动端不友好。
2. JWT(推荐)
JWT(JSON Web Token)是无状态的鉴权方案,适合前后端分离:
- 用户登录后服务端签发JWT
- 前端保存在 localStorage 或 Cookie
- 每次请求在Header中携带:
Authorization: Bearer <token> - 服务端验证签名 + 过期时间
// 生成
const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: 123 }, SECRET, { expiresIn: '7d' });
// 验证
try {
const payload = jwt.verify(token, SECRET);
// payload.userId 可用
} catch (e) {
// token无效或过期
}
3. API Key
开放API常用,调用方持有Key,服务端校验。一般配合限流使用。
六、参数校验与错误处理
1. 参数来源
- Query:URL问号后,多用于筛选/分页:?page=1&size=10
- Path:URL路径中:/users/:id
- Body:请求体,POST/PUT/PATCH
- Header:鉴权信息、Content-Type
2. 校验示例 (Joi)
const Joi = require('joi');
const schema = Joi.object({
name: Joi.string().min(2).max(20).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150),
role: Joi.string().valid('user', 'admin').default('user'),
});
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(422).json({
code: 422,
message: error.details[0].message,
data: null,
});
}
// 使用 value
3. 全局错误中间件
// 统一错误处理
app.use((err, req, res, next) => {
console.error(err);
res.status(err.status || 500).json({
code: err.code || 500,
message: err.message || '服务异常',
data: null,
});
});
七、版本管理
接口不可避免会迭代,需要版本管理避免破坏老前端:
1. URL版本(推荐)
/api/v1/users
/api/v2/users
2. Header版本
Accept: application/vnd.myapp.v2+json
3. 弃用策略
- 在响应头标记弃用:
Deprecation: true、Sunset: <date> - 文档明确弃用时间,留至少6个月迁移期
- 监控旧版本调用量,归零后再下线
八、实战:Node后端完整示例
// app.js
const express = require('express');
const jwt = require('jsonwebtoken');
const Joi = require('joi');
const app = express();
app.use(express.json());
const SECRET = 'your-secret-key';
const USERS = [{ id: 1, name: '信仰', email: 'admin@test.com', password: '123456' }];
// 中间件:JWT鉴权
function auth(req, res, next) {
const header = req.headers.authorization || '';
const token = header.replace(/^Bearer\s/, '');
if (!token) return res.status(401).json({ code: 401, message: '未登录', data: null });
try {
req.user = jwt.verify(token, SECRET);
next();
} catch {
res.status(401).json({ code: 401, message: 'token无效', data: null });
}
}
// 成功响应封装
function ok(data, message = 'ok') {
return { code: 0, message, data };
}
function fail(code, message) {
return { code, message, data: null };
}
// 登录
app.post('/api/v1/auth/login', (req, res) => {
const { email, password } = req.body;
const user = USERS.find(u => u.email === email && u.password === password);
if (!user) return res.status(401).json(fail(40101, '账号或密码错误'));
const token = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '7d' });
res.json(ok({ token, user: { id: user.id, name: user.name, email: user.email } }));
});
// 用户列表
app.get('/api/v1/users', auth, (req, res) => {
const page = +req.query.page || 1;
const size = +req.query.size || 10;
const list = USERS.slice((page - 1) * size, page * size);
res.json(ok({ list, total: USERS.length, page, pageSize: size }));
});
// 用户详情
app.get('/api/v1/users/:id', auth, (req, res) => {
const user = USERS.find(u => u.id === +req.params.id);
if (!user) return res.status(404).json(fail(40401, '用户不存在'));
res.json(ok(user));
});
// 创建用户
app.post('/api/v1/users', auth, (req, res) => {
const schema = Joi.object({
name: Joi.string().min(2).max(20).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
const { error, value } = schema.validate(req.body);
if (error) return res.status(422).json(fail(42200, error.details[0].message));
const user = { id: Date.now(), ...value };
USERS.push(user);
res.status(201).json(ok(user, '创建成功'));
});
// 全局错误处理
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json(fail(500, '服务异常'));
});
app.listen(3000, () => console.log('API on http://localhost:3000'));
九、实战:Vue前端调用示例
1. axios封装
// src/utils/request.js
import axios from 'axios';
import { ElMessage } from 'element-plus';
const request = axios.create({
baseURL: '/api/v1',
timeout: 15000,
});
// 请求拦截:自动加Token
request.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 响应拦截:统一处理
request.interceptors.response.use(
response => {
const { code, message, data } = response.data;
if (code === 0) return data;
// 业务错误
ElMessage.error(message);
return Promise.reject(new Error(message));
},
error => {
const status = error.response?.status;
if (status === 401) {
// 跳转登录
localStorage.removeItem('token');
location.href = '/login';
} else {
ElMessage.error(error.response?.data?.message || '网络错误');
}
return Promise.reject(error);
}
);
export default request;
2. 接口定义层
// src/api/user.js
import request from '@/utils/request';
export function login(data) {
return request.post('/auth/login', data);
}
export function getUsers(params) {
return request.get('/users', { params });
}
export function getUser(id) {
return request.get(`/users/${id}`);
}
export function createUser(data) {
return request.post('/users', data);
}
3. 组件使用
<script setup>
import { ref, onMounted } from 'vue';
import { getUsers } from '@/api/user';
const users = ref([]);
const loading = ref(false);
async function load() {
loading.value = true;
try {
const { list, total } = await getUsers({ page: 1, size: 10 });
users.value = list;
} finally {
loading.value = false;
}
}
onMounted(load);
</script>
<template>
<div v-loading="loading">
<div v-for="u in users" :key="u.id">{{ u.name }} - {{ u.email }}</div>
</div>
</template>
十、性能与安全最佳实践
1. 性能
- 列表接口务必分页,避免一次拉几万条
- 响应启用gzip/brotli压缩
- 静态资源走CDN,接口用HTTP/2或HTTP/3
- 热点数据加缓存(Redis)
2. 安全
- 所有接口必须HTTPS
- 敏感字段加密传输(密码、身份证)
- 防SQL注入:使用参数化查询,禁止拼接SQL
- 防XSS:响应转义、设置CSP
- 防CSRF:SameSite Cookie / CSRF Token
- 限流:单IP/单用户单位时间请求次数
- 接口幂等:POST接口用幂等Key防重复提交
十一、接口文档
好接口离不开好文档。推荐方案:
- Swagger/OpenAPI:从代码注解自动生成文档,可在线调试
- Apifox/Postman:团队协作 + Mock数据
- 文档需包含:URL、方法、参数说明、响应示例、错误码表
总结
API是前后端协作的契约,规范的设计能让协作事半功倍。掌握RESTful设计原则、HTTP方法语义、状态码体系、统一响应格式、JWT鉴权、参数校验、版本管理、文档与安全实践,就能设计出既好对接又好维护的接口体系。多看优秀开源项目(GitHub API、Stripe API)的接口设计,是提升API设计感的最佳途径。