# Backend API Design Comprehensive guide to designing RESTful, GraphQL, and gRPC APIs with best practices (2025). ## REST API Design ### Resource-Based URLs **Good:** ``` GET /api/v1/users # List users GET /api/v1/users/:id # Get specific user POST /api/v1/users # Create user PUT /api/v1/users/:id # Update user (full) PATCH /api/v1/users/:id # Update user (partial) DELETE /api/v1/users/:id # Delete user GET /api/v1/users/:id/posts # Get user's posts POST /api/v1/users/:id/posts # Create post for user ``` **Bad (Avoid):** ``` GET /api/v1/getUser?id=123 # RPC-style, not RESTful POST /api/v1/createUser # Verb in URL GET /api/v1/user-posts # Unclear relationship ``` ### HTTP Status Codes (Meaningful Responses) **Success:** - `200 OK` - Successful GET, PUT, PATCH - `201 Created` - Successful POST (resource created) - `204 No Content` - Successful DELETE **Client Errors:** - `400 Bad Request` - Invalid input/validation error - `401 Unauthorized` - Missing or invalid authentication - `403 Forbidden` - Authenticated but not authorized - `404 Not Found` - Resource doesn't exist - `409 Conflict` - Resource conflict (duplicate email) - `422 Unprocessable Entity` - Validation error (detailed) - `429 Too Many Requests` - Rate limit exceeded **Server Errors:** - `500 Internal Server Error` - Generic server error - `502 Bad Gateway` - Upstream service error - `503 Service Unavailable` - Temporary downtime - `504 Gateway Timeout` - Upstream service timeout ### Request/Response Format **Request:** ```typescript POST /api/v1/users Content-Type: application/json { "email": "user@example.com", "name": "John Doe", "age": 30 } ``` **Success Response:** ```typescript HTTP/1.1 201 Created Content-Type: application/json Location: /api/v1/users/123 { "id": "123", "email": "user@example.com", "name": "John Doe", "age": 30, "createdAt": "2025-01-09T12:00:00Z", "updatedAt": "2025-01-09T12:00:00Z" } ``` **Error Response:** ```typescript HTTP/1.1 400 Bad Request Content-Type: application/json { "error": { "code": "VALIDATION_ERROR", "message": "Invalid input data", "details": [ { "field": "email", "message": "Invalid email format", "value": "invalid-email" }, { "field": "age", "message": "Age must be between 18 and 120", "value": 15 } ], "timestamp": "2025-01-09T12:00:00Z", "path": "/api/v1/users" } } ``` ### Pagination ```typescript // Request GET /api/v1/users?page=2&limit=50 // Response { "data": [...], "pagination": { "page": 2, "limit": 50, "total": 1234, "totalPages": 25, "hasNext": true, "hasPrev": true }, "links": { "first": "/api/v1/users?page=1&limit=50", "prev": "/api/v1/users?page=1&limit=50", "next": "/api/v1/users?page=3&limit=50", "last": "/api/v1/users?page=25&limit=50" } } ``` ### Filtering and Sorting ``` GET /api/v1/users?status=active&role=admin&sort=-createdAt,name&limit=20 # Filters: status=active AND role=admin # Sort: createdAt DESC, name ASC # Limit: 20 results ``` ### API Versioning Strategies **URL Versioning (Most Common):** ``` /api/v1/users /api/v2/users ``` **Header Versioning:** ``` GET /api/users Accept: application/vnd.myapi.v2+json ``` **Query Parameter:** ``` /api/users?version=2 ``` **Recommendation:** URL versioning for simplicity and discoverability ## GraphQL API Design ### Schema Definition ```graphql type User { id: ID! email: String! name: String! posts: [Post!]! createdAt: DateTime! } type Post { id: ID! title: String! content: String! author: User! published: Boolean! createdAt: DateTime! } type Query { user(id: ID!): User users(limit: Int = 50, offset: Int = 0): [User!]! post(id: ID!): Post posts(authorId: ID, published: Boolean): [Post!]! } type Mutation { createUser(input: CreateUserInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User! deleteUser(id: ID!): Boolean! createPost(input: CreatePostInput!): Post! publishPost(id: ID!): Post! } input CreateUserInput { email: String! name: String! password: String! } input UpdateUserInput { email: String name: String } ``` ### Queries ```graphql # Flexible data fetching - client specifies exactly what they need query { user(id: "123") { id name email posts { id title published } } } # With variables query GetUser($userId: ID!) { user(id: $userId) { id name posts(published: true) { title } } } ``` ### Mutations ```graphql mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id email name createdAt } } # Variables { "input": { "email": "user@example.com", "name": "John Doe", "password": "SecurePass123!" } } ``` ### Resolvers (NestJS Example) ```typescript @Resolver(() => User) export class UserResolver { constructor( private userService: UserService, private postService: PostService, ) {} @Query(() => User, { nullable: true }) async user(@Args('id') id: string) { return this.userService.findById(id); } @Query(() => [User]) async users( @Args('limit', { defaultValue: 50 }) limit: number, @Args('offset', { defaultValue: 0 }) offset: number, ) { return this.userService.findAll({ limit, offset }); } @Mutation(() => User) async createUser(@Args('input') input: CreateUserInput) { return this.userService.create(input); } // Field resolver - lazy load posts @ResolveField(() => [Post]) async posts(@Parent() user: User) { return this.postService.findByAuthorId(user.id); } } ``` ### GraphQL Best Practices 1. **Avoid N+1 Problem** - Use DataLoader ```typescript import DataLoader from 'dataloader'; const postLoader = new DataLoader(async (authorIds: string[]) => { const posts = await db.posts.findAll({ where: { authorId: authorIds } }); return authorIds.map(id => posts.filter(p => p.authorId === id)); }); // In resolver @ResolveField(() => [Post]) async posts(@Parent() user: User) { return this.postLoader.load(user.id); } ``` 2. **Pagination** - Relay-style cursor pagination 3. **Error Handling** - Return errors in response 4. **Depth Limiting** - Prevent deeply nested queries 5. **Query Complexity Analysis** - Limit expensive queries ## gRPC API Design ### Protocol Buffers Schema ```protobuf syntax = "proto3"; package user; service UserService { rpc GetUser (GetUserRequest) returns (User); rpc ListUsers (ListUsersRequest) returns (ListUsersResponse); rpc CreateUser (CreateUserRequest) returns (User); rpc UpdateUser (UpdateUserRequest) returns (User); rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse); // Streaming rpc StreamUsers (StreamUsersRequest) returns (stream User); } message User { string id = 1; string email = 2; string name = 3; int64 created_at = 4; } message GetUserRequest { string id = 1; } message ListUsersRequest { int32 limit = 1; int32 offset = 2; } message ListUsersResponse { repeated User users = 1; int32 total = 2; } message CreateUserRequest { string email = 1; string name = 2; string password = 3; } ``` ### Implementation (Node.js) ```typescript import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; const packageDefinition = protoLoader.loadSync('user.proto'); const userProto = grpc.loadPackageDefinition(packageDefinition).user; // Server implementation const server = new grpc.Server(); server.addService(userProto.UserService.service, { async getUser(call, callback) { const user = await userService.findById(call.request.id); callback(null, user); }, async createUser(call, callback) { const user = await userService.create(call.request); callback(null, user); }, async streamUsers(call) { const users = await userService.findAll(); for (const user of users) { call.write(user); } call.end(); }, }); server.bindAsync( '0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => server.start() ); ``` ### gRPC Benefits - **Performance:** 7-10x faster than REST (binary protocol) - **Streaming:** Bi-directional streaming - **Type Safety:** Strong typing via Protocol Buffers - **Code Generation:** Auto-generate client/server code - **Best For:** Internal microservices, high-performance systems ## API Design Decision Matrix | Feature | REST | GraphQL | gRPC | |---------|------|---------|------| | **Use Case** | Public APIs, CRUD | Flexible data fetching | Microservices, performance | | **Performance** | Moderate | Moderate | Fastest (7-10x REST) | | **Caching** | HTTP caching built-in | Complex | No built-in caching | | **Browser Support** | Native | Native | Requires gRPC-Web | | **Learning Curve** | Easy | Moderate | Steep | | **Streaming** | Limited (SSE) | Subscriptions | Bi-directional | | **Tooling** | Excellent | Excellent | Good | | **Documentation** | OpenAPI/Swagger | Schema introspection | Protobuf definition | ## API Security Checklist - [ ] HTTPS/TLS only (no HTTP) - [ ] Authentication (OAuth 2.1, JWT, API keys) - [ ] Authorization (RBAC, check permissions) - [ ] Rate limiting (prevent abuse) - [ ] Input validation (all endpoints) - [ ] CORS configured properly - [ ] Security headers (CSP, HSTS, X-Frame-Options) - [ ] API versioning implemented - [ ] Error messages don't leak system info - [ ] Audit logging (who did what, when) ## API Documentation ### OpenAPI/Swagger (REST) ```yaml openapi: 3.0.0 info: title: User API version: 1.0.0 paths: /api/v1/users: get: summary: List users parameters: - name: limit in: query schema: type: integer default: 50 responses: '200': description: Successful response content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/User' components: schemas: User: type: object properties: id: type: string email: type: string name: type: string ``` ## Resources - **REST Best Practices:** https://restfulapi.net/ - **GraphQL:** https://graphql.org/learn/ - **gRPC:** https://grpc.io/docs/ - **OpenAPI:** https://swagger.io/specification/