REST、GraphQL、WebSocket详解 | 从零基础到精通 | 包含设计、安全和优化
API(应用程序编程接口)是两个软件系统之间的通信协议。它定义了请求和响应的格式,允许不同的应用相互通信。
// 资源命名
GET /api/users # 获取所有用户
GET /api/users/1 # 获取用户1
POST /api/users # 创建用户
PUT /api/users/1 # 更新用户1
DELETE /api/users/1 # 删除用户1
// 查询参数
GET /api/users?page=1&limit=10&sort=created_at
GET /api/users?filter[status]=active
GET /api/users?search=alice
// 子资源
GET /api/users/1/posts # 获取用户1的所有文章
POST /api/users/1/posts # 为用户1创建文章
GET /api/users/1/posts/5 # 获取用户1的文章5
// 响应格式
{
"success": true,
"data": {
"id": 1,
"username": "alice",
"email": "alice@example.com"
},
"meta": {
"page": 1,
"limit": 10,
"total": 100
}
}
// 错误响应
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": {
"email": "Email is invalid"
}
}
}// 基于偏移的分页
GET /api/users?offset=0&limit=10
// 基于游标的分页(推荐)
GET /api/users?cursor=abc123&limit=10
// 响应
{
"data": [...],
"pagination": {
"cursor": "xyz789",
"has_more": true,
"limit": 10
}
}GraphQL是一个查询语言和运行时,允许客户端精确指定需要的数据。
| 特性 | GraphQL | REST |
|---|---|---|
| 数据获取 | 精确获取需要的字段 | 固定的响应结构 |
| 多个资源 | 单个请求 | 多个请求 |
| 学习曲线 | 陡峭 | 平缓 |
| 缓存 | 复杂 | 简单 |
// Schema定义
type User {
id: ID!
username: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(username: String!, email: String!): User!
updateUser(id: ID!, username: String): User
deleteUser(id: ID!): Boolean!
}
// 查询
query {
user(id: "1") {
id
username
email
posts {
id
title
}
}
}
// 响应
{
"data": {
"user": {
"id": "1",
"username": "alice",
"email": "alice@example.com",
"posts": [
{
"id": "1",
"title": "First Post"
}
]
}
}
}const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type User {
id: ID!
username: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(username: String!, email: String!): User!
}
`;
const resolvers = {
Query: {
user: (_, { id }) => {
return { id, username: 'alice', email: 'alice@example.com' };
},
users: () => {
return [
{ id: '1', username: 'alice', email: 'alice@example.com' }
];
}
},
Mutation: {
createUser: (_, { username, email }) => {
return { id: '2', username, email };
}
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});const WebSocket = require('ws');
const http = require('http');
const server = http.createServer();
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => {
console.log('Client connected');
// 接收消息
ws.on('message', (message) => {
console.log('Received:', message);
// 广播给所有客户端
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
// 处理断开连接
ws.on('close', () => {
console.log('Client disconnected');
});
// 发送欢迎消息
ws.send('Welcome to the server');
});
server.listen(8080, () => {
console.log('Server listening on port 8080');
});// 浏览器客户端
const ws = new WebSocket('ws://localhost:8080');
// 连接打开
ws.onopen = () => {
console.log('Connected');
ws.send('Hello Server');
};
// 接收消息
ws.onmessage = (event) => {
console.log('Message from server:', event.data);
};
// 连接关闭
ws.onclose = () => {
console.log('Disconnected');
};
// 错误处理
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};const io = require('socket.io')(3000);
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// 监听事件
socket.on('message', (data) => {
console.log('Message:', data);
// 广播给所有客户端
io.emit('message', data);
});
// 监听断开连接
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});// 添加新字段时保持兼容性
// v1响应
{
"id": 1,
"username": "alice",
"email": "alice@example.com"
}
// v2响应(添加新字段)
{
"id": 1,
"username": "alice",
"email": "alice@example.com",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z"
}
// 弃用字段时使用Deprecation头
app.get('/api/v1/users/:id', (req, res) => {
res.set('Deprecation', 'true');
res.set('Sunset', new Date(Date.now() + 30*24*60*60*1000).toUTCString());
res.json({ /* ... */ });
});// 请求头中传递API密钥
GET /api/users
Authorization: Bearer sk_live_abc123xyz789
// 验证中间件
const authenticateApiKey = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token' });
}
// 验证token
const user = verifyToken(token);
if (!user) {
return res.status(401).json({ error: 'Invalid token' });
}
req.user = user;
next();
};
app.use(authenticateApiKey);// 授权码流程
1. 用户点击"使用Google登录"
2. 重定向到Google授权页面
3. 用户授权后,Google重定向回应用,带上授权码
4. 应用使用授权码换取访问令牌
5. 使用访问令牌访问API
// 实现示例
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
// 创建或更新用户
User.findOrCreate({ googleId: profile.id }, (err, user) => {
return done(err, user);
});
}));// 基于角色的访问控制(RBAC)
const authorize = (roles) => {
return (req, res, next) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
// 使用
app.delete('/api/users/:id',
authenticate,
authorize(['admin']),
(req, res) => {
// 删除用户
}
);const rateLimit = require('express-rate-limit');
// 创建限制器
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 限制100个请求
message: 'Too many requests'
});
// 应用到所有请求
app.use(limiter);
// 应用到特定路由
app.post('/api/login',
rateLimit({
windowMs: 15 * 60 * 1000,
max: 5 // 登录限制更严格
}),
(req, res) => {
// 登录逻辑
}
);// HTTP缓存头
app.get('/api/users/:id', (req, res) => {
// 缓存1小时
res.set('Cache-Control', 'public, max-age=3600');
// 或者使用ETag进行条件请求
const etag = generateETag(user);
res.set('ETag', etag);
if (req.headers['if-none-match'] === etag) {
return res.status(304).send();
}
res.json(user);
});
// 使用Redis缓存
const redis = require('redis');
const client = redis.createClient();
app.get('/api/users/:id', async (req, res) => {
const cacheKey = `user:${req.params.id}`;
// 检查缓存
const cached = await client.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
// 从数据库获取
const user = await User.findById(req.params.id);
// 缓存1小时
await client.setEx(cacheKey, 3600, JSON.stringify(user));
res.json(user);
});// swagger.yaml
openapi: 3.0.0
info:
title: My API
version: 1.0.0
paths:
/api/users:
get:
summary: 获取所有用户
parameters:
- name: page
in: query
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: 创建用户
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserInput'
responses:
'201':
description: 创建成功
components:
schemas:
User:
type: object
properties:
id:
type: integer
username:
type: string
email:
type: stringconst swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));const request = require('supertest');
const app = require('../app');
describe('User API', () => {
test('GET /api/users should return all users', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body).toBeInstanceOf(Array);
});
test('POST /api/users should create a user', async () => {
const response = await request(app)
.post('/api/users')
.send({
username: 'alice',
email: 'alice@example.com'
})
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.username).toBe('alice');
});
test('GET /api/users/:id should return a user', async () => {
const response = await request(app)
.get('/api/users/1')
.expect(200);
expect(response.body).toHaveProperty('id');
});
});// routes/users.js
const express = require('express');
const router = express.Router();
const { authenticate, authorize } = require('../middleware/auth');
// 获取所有用户
router.get('/', async (req, res, next) => {
try {
const { page = 1, limit = 10 } = req.query;
const skip = (page - 1) * limit;
const users = await User.find()
.skip(skip)
.limit(limit);
const total = await User.countDocuments();
res.json({
data: users,
pagination: { page, limit, total }
});
} catch (error) {
next(error);
}
});
// 创建用户
router.post('/', async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (error) {
next(error);
}
});
// 更新用户
router.put('/:id', authenticate, authorize(['admin']), async (req, res, next) => {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true }
);
res.json(user);
} catch (error) {
next(error);
}
});
// 删除用户
router.delete('/:id', authenticate, authorize(['admin']), async (req, res, next) => {
try {
await User.findByIdAndDelete(req.params.id);
res.json({ success: true });
} catch (error) {
next(error);
}
});
module.exports = router;通过这个项目,你学会了如何设计和实现一个完整的、生产级别的API系统。
现在你已经掌握了API开发的核心知识。
API设计的关键是考虑用户体验。设计简洁、易用的API,会让开发者更愿意使用你的服务。