Difficulty: Intermediate
Design a RESTful API for a blog platform. Compare REST, GraphQL, and WebSocket approaches.
REST: Resource-based URLs with HTTP verbs. Stateless. Best for CRUD operations and public APIs.
GraphQL: Single endpoint where the client specifies exact data needed. Best for complex data requirements and mobile apps.
WebSocket: Persistent bidirectional connection. Best for real-time features like chat, notifications, live updates.
gRPC: Binary protocol using Protocol Buffers. Fast and typed. Best for internal service-to-service communication.
API Design Principles: 1. Use nouns for resources, HTTP verbs for actions 2. Use proper HTTP status codes 3. Support filtering, sorting, pagination 4. Version your API 5. Use consistent response format 6. Implement rate limiting
// Resource naming: nouns, not verbs
GET /api/v1/posts // list posts
GET /api/v1/posts/:id // get single post
POST /api/v1/posts // create post
PUT /api/v1/posts/:id // full update
PATCH /api/v1/posts/:id // partial update
DELETE /api/v1/posts/:id // delete post
// Nested resources
GET /api/v1/posts/:id/comments // post comments
POST /api/v1/posts/:id/comments // add comment
// Filtering, sorting, pagination
GET /api/v1/posts?status=published&sort=-created_at&page=2&limit=20
// Cursor-based pagination (better for large datasets)
GET /api/v1/posts?after=cursor_abc&limit=20
// Consistent response format
{
"data": [{ "id": 1, "title": "..." }],
"pagination": {
"page": 2,
"limit": 20,
"total": 200,
"totalPages": 10
},
"error": null
}
// Error response format
{
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Title is required",
"details": [{ "field": "title", "message": "Required" }]
}
}
Consistent REST design makes APIs predictable and self-documenting. Use proper HTTP status codes and a uniform response envelope.
// REST: Multiple requests, potential over-fetching
GET /api/posts/123 // full post object
GET /api/posts/123/comments // all comments
GET /api/users/456 // author details
// 3 requests, lots of unused fields
// GraphQL: Single request, exact data
query {
post(id: "123") {
title
content
author {
name
avatar
}
comments(first: 5) {
text
user { name }
}
}
}
// 1 request, only requested fields
// GraphQL trade-offs:
// + No over-fetching or under-fetching
// + Strongly typed schema
// + Great for mobile (bandwidth savings)
// - No HTTP caching (everything is POST)
// - N+1 query problem (use DataLoader)
// - Complex server implementation
// - Harder to rate-limit (single endpoint)
GraphQL excels when clients need flexible data access. REST is simpler and better for CRUD-heavy APIs with predictable data shapes.
// Server (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const rooms = new Map(); // roomId -> Set of clients
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const msg = JSON.parse(data);
switch (msg.type) {
case 'JOIN_ROOM':
if (!rooms.has(msg.room)) rooms.set(msg.room, new Set());
rooms.get(msg.room).add(ws);
ws.room = msg.room;
break;
case 'MESSAGE':
const clients = rooms.get(ws.room) || [];
clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
user: msg.user,
text: msg.text,
timestamp: Date.now()
}));
}
});
break;
}
});
ws.on('close', () => {
if (ws.room && rooms.has(ws.room)) {
rooms.get(ws.room).delete(ws);
}
});
});
WebSocket maintains a persistent connection. Unlike HTTP request-response, both client and server can push messages at any time. Room-based routing lets you scope broadcasts.
REST, GraphQL, WebSocket, gRPC, Pagination, Versioning