API Design & REST
Building Roads for Your Data
Open interactive version (quiz + challenge)Real-world analogy
An API is like a restaurant menu. The customer (frontend) looks at the menu (API docs) and orders (makes a request). The kitchen (backend) prepares the food (data) and serves it. The customer never goes INTO the kitchen — they just use the menu!
What is it?
REST (Representational State Transfer) is an architectural style for designing APIs. It uses HTTP methods and URLs to represent resources and actions. Combined with proper status codes and DTOs, it creates predictable, developer-friendly APIs.
Real-world relevance
Twitter, GitHub, Stripe, and virtually every modern web service exposes a REST API. Stripe's API is often cited as the gold standard of API design.
Key points
- REST Principles — REST uses HTTP methods meaningfully: GET reads data, POST creates, PUT replaces, PATCH partially updates, DELETE removes. This convention makes APIs clean, predictable, and self-documenting for any developer.
- Status Codes — HTTP status codes tell clients what happened: 200 OK, 201 created, 400 bad request, 401 unauthorized, 403 forbidden, 404 not found, 409 conflict, 500 server error. Always return the correct code!
- DTOs (Data Transfer Objects) — DTOs define the exact shape of request and response data using TypeScript classes. They act as contracts between frontend and backend, specifying required fields, types, and validation rules.
- Swagger/OpenAPI — Auto-generate interactive API docs from your code using decorators like @ApiTags and @ApiOperation. Frontend devs can explore endpoints, test requests, and see response schemas without reading backend code.
- Pagination — For large datasets, return items in pages: ?page=1&limit=20. Include metadata like total items, current page, total pages, and hasNextPage. Never send millions of records in a single response!
- Filtering & Sorting — Let clients request exactly what they need: ?status=active&sort=createdAt&order=desc. Implement flexible query params so frontends can filter, sort, and search without separate endpoints for each case.
- Versioning Your API — As your API evolves, old clients may break. Use URL versioning (/api/v1/users, /api/v2/users) or header versioning for backward compatibility. Ship new features without breaking existing apps.
- Error Response Format — Return consistent error objects: { statusCode, message, error, timestamp }. Include helpful details for frontends to display meaningful messages. Never expose raw stack traces or DB errors in production.
- Rate Limiting — Protect your API from abuse by limiting requests per time window. Use @nestjs/throttler for limits like 100 requests per 60 seconds. Return 429 Too Many Requests when exceeded to prevent server overload.
- CORS (Cross-Origin Resource Sharing) — Browsers block cross-domain requests by default. Configure CORS in NestJS with app.enableCors() to allow your frontend origin. Essential when React runs on localhost:3000 and NestJS on localhost:4000.
Code example
// RESTful routes — clean and predictable 📐
@Controller('products')
export class ProductController {
@Get() // GET /products → list all
findAll(@Query() query: FilterDto) {
return this.productService.findAll(query);
}
@Get(':id') // GET /products/123 → get one
findOne(@Param('id') id: string) {
return this.productService.findOne(id);
}
@Post() // POST /products → create
create(@Body() dto: CreateProductDto) {
return this.productService.create(dto);
}
@Put(':id') // PUT /products/123 → update
update(@Param('id') id: string, @Body() dto: UpdateProductDto) {
return this.productService.update(id, dto);
}
@Delete(':id') // DELETE /products/123 → delete
remove(@Param('id') id: string) {
return this.productService.remove(id);
}
}
// DTO with validation — reject bad data at the door! 🚫
class CreateProductDto {
@IsString()
@MinLength(2)
name: string;
@IsNumber()
@Min(0)
price: number;
@IsOptional()
@IsString()
description?: string;
}
// Swagger — auto-generated docs! 📚
@ApiTags('Products')
@ApiOperation({ summary: 'Create a new product' })
@ApiResponse({ status: 201, description: 'Product created' })
@ApiResponse({ status: 400, description: 'Invalid data' })
@Post()
create(@Body() dto: CreateProductDto) { ... }Line-by-line walkthrough
- 1. RESTful routes — clean and predictable 📐
- 2. Decorator that adds metadata or behavior
- 3. Exporting for use in other files
- 4. Decorator that adds metadata or behavior
- 5.
- 6. Returning a value
- 7. Closing block
- 8.
- 9. Decorator that adds metadata or behavior
- 10.
- 11. Returning a value
- 12. Closing block
- 13.
- 14. Decorator that adds metadata or behavior
- 15.
- 16. Returning a value
- 17. Closing block
- 18.
- 19. Decorator that adds metadata or behavior
- 20.
- 21. Returning a value
- 22. Closing block
- 23.
- 24. Decorator that adds metadata or behavior
- 25.
- 26. Returning a value
- 27. Closing block
- 28. Closing block
- 29.
- 30. DTO with validation — reject bad data at the door! 🚫
- 31. Declaring a class
- 32. Decorator that adds metadata or behavior
- 33. Decorator that adds metadata or behavior
- 34.
- 35.
- 36. Decorator that adds metadata or behavior
- 37. Decorator that adds metadata or behavior
- 38.
- 39.
- 40. Decorator that adds metadata or behavior
- 41. Decorator that adds metadata or behavior
- 42.
- 43. Closing block
- 44.
- 45. Swagger — auto-generated docs! 📚
- 46. Decorator that adds metadata or behavior
- 47. Decorator that adds metadata or behavior
- 48. Decorator that adds metadata or behavior
- 49. Decorator that adds metadata or behavior
- 50. Decorator that adds metadata or behavior
- 51.
Spot the bug
@Controller('users')
export class UserController {
@Post(':id')
deleteUser(@Param('id') id: string) {
return this.userService.delete(id);
}
}Need a hint?
Does the HTTP method match the action being performed?
Show answer
The method uses @Post() but deletes a user. POST is for creating, DELETE is for deleting. Fix: change @Post(':id') to @Delete(':id').
Explain like I'm 5
An API is like a restaurant menu. You look at the menu and tell the waiter 'I want chicken' (make a request). The kitchen makes it and the waiter brings it back. You never go into the kitchen yourself - you just use the menu to order!
Fun fact
The GitHub API has over 1,000 endpoints. Stripe's API documentation is so good that it's used as a teaching example in university computer science courses! 📚
Hands-on challenge
Design a REST API for a 'bookstore' on paper first: what endpoints would you need for books, authors, and orders? Then implement the books CRUD in NestJS!
More resources
- MDN HTTP Overview (MDN Web Docs)
- REST API Design Best Practices (RESTful API)
- RESTful APIs in 100 Seconds (Fireship)