前后端API接口详解

前后端分离架构下,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 }

五、鉴权方案

传统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: trueSunset: <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设计感的最佳途径。