Fundamental principles of REST API design
A well-designed REST API is predictable, consistent, and easy to consume. It is not just about returning JSON from a server: it is about creating a clear contract between your backend and the clients that consume it.
Resource naming
Rules for URLs
Endpoints should be plural nouns representing resources, not actions:
GOOD:
GET /api/v1/users -> list users
GET /api/v1/users/123 -> get a user
POST /api/v1/users -> create a user
PUT /api/v1/users/123 -> update a full user
PATCH /api/v1/users/123 -> partial update
DELETE /api/v1/users/123 -> delete a user
BAD:
GET /api/v1/getUsers
POST /api/v1/createUser
POST /api/v1/deleteUser/123
GET /api/v1/user/listNested resources
For relationships, use nesting up to two levels maximum:
GET /api/v1/users/123/courses -> user's courses
GET /api/v1/courses/456/lessons -> course lessons
GET /api/v1/courses/456/lessons/789 -> a specific lesson
# Avoid excessive nesting
BAD: /api/v1/users/123/courses/456/lessons/789/comments
GOOD: /api/v1/lessons/789/commentsNaming conventions
| Rule | Example |
|---|---|
| Use kebab-case | /learning-paths not /learningPaths |
| Plural nouns | /courses not /course |
| Lowercase | /blog-posts not /Blog-Posts |
| No trailing slash | /users not /users/ |
| No extensions | /users not /users.json |
Correct HTTP methods
| Method | Usage | Idempotent | Body |
|---|---|---|---|
| GET | Read resource(s) | Yes | No |
| POST | Create resource | No | Yes |
| PUT | Replace full resource | Yes | Yes |
| PATCH | Partial update | No* | Yes |
| DELETE | Delete resource | Yes | No |
| HEAD | Check existence | Yes | No |
| OPTIONS | Get allowed methods | Yes | No |
*PATCH can be idempotent depending on the implementation.
HTTP status codes
Using the correct codes is fundamental so that clients know how to handle each response:
Success (2xx)
| Code | Usage |
|---|---|
| 200 OK | Successful request with body |
| 201 Created | Resource created (POST) |
| 204 No Content | Success without body (DELETE, PUT) |
Redirection (3xx)
| Code | Usage |
|---|---|
| 301 Moved Permanently | Resource permanently moved |
| 304 Not Modified | Valid cache, do not send body |
Client error (4xx)
| Code | Usage |
|---|---|
| 400 Bad Request | Malformed JSON or invalid parameters |
| 401 Unauthorized | Not authenticated |
| 403 Forbidden | Authenticated but without permissions |
| 404 Not Found | Resource does not exist |
| 409 Conflict | Conflict (e.g., duplicate email) |
| 422 Unprocessable Entity | Validation failed |
| 429 Too Many Requests | Rate limit exceeded |
Server error (5xx)
| Code | Usage |
|---|---|
| 500 Internal Server Error | Unhandled error |
| 502 Bad Gateway | Upstream service failed |
| 503 Service Unavailable | Service temporarily unavailable |
Response structure
Successful response
{
"data": {
"id": "crs_abc123",
"type": "course",
"attributes": {
"title": "HTML desde cero",
"level": "beginner",
"lessonsCount": 12
}
}
}Paginated list response
{
"data": [
{ "id": "crs_001", "title": "HTML desde cero" },
{ "id": "crs_002", "title": "CSS Fundamentals" }
],
"pagination": {
"page": 1,
"perPage": 20,
"totalPages": 5,
"totalItems": 94,
"hasNextPage": true,
"hasPrevPage": false
},
"links": {
"self": "/api/v1/courses?page=1",
"next": "/api/v1/courses?page=2",
"last": "/api/v1/courses?page=5"
}
}Standardized error response
{
"error": {
"status": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "El curso solicitado no existe.",
"details": []
}
}Pagination
There are three main strategies:
1. Offset-based (the simplest)
GET /api/v1/courses?page=2&per_page=20Advantages: simple to implement. Disadvantages: inefficient on large tables, inconsistent results if data is inserted.
2. Cursor-based (the most robust)
GET /api/v1/courses?cursor=eyJpZCI6MTIzfQ&limit=20Advantages: consistent performance, stable results. Disadvantages: cannot jump to a specific page.
3. Keyset pagination
GET /api/v1/courses?after_id=crs_123&limit=20Similar to cursor but with an explicit field as key.
Filtering, sorting, and search
Filtering
GET /api/v1/courses?level=beginner&category=frontend
GET /api/v1/courses?created_after=2025-01-01
GET /api/v1/courses?tags=html,cssSorting
GET /api/v1/courses?sort=created_at # ascending
GET /api/v1/courses?sort=-created_at # descending
GET /api/v1/courses?sort=-rating,title # multiple fieldsSearch
GET /api/v1/courses?q=angular+signals
GET /api/v1/search?q=javascript&type=course,blogVersioning
In the URL (recommended)
GET /api/v1/courses
GET /api/v2/coursesSimple, explicit, and easy to cache. This is the strategy most used by APIs like Stripe, GitHub, and Twilio.
In headers (alternative)
GET /api/courses
Accept: application/vnd.bemorex.v1+jsonMore semantically "pure" but harder to use and test.
Security
Authentication with Bearer tokens
GET /api/v1/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Security headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'Rate limiting
Include informational headers in every response:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1706810400
Retry-After: 60Input validation
- Always validate on the server, never trust the client alone
- Sanitize inputs to prevent XSS and SQL injection
- Limit body size (e.g., 1MB for JSON, 10MB for files)
- Validate Content-Type
Documentation
A good API is a well-documented API. Use OpenAPI (Swagger) to generate interactive documentation:
openapi: 3.1.0
info:
title: Bemore Learn API
versión: 1.0.0
paths:
/api/v1/courses:
get:
summary: Listar cursos
parameters:
- name: level
in: query
schema:
type: string
enum: [beginner, intermediate, advanced]
responses:
'200':
description: Lista de cursosConclusion
Designing a good REST API requires discipline and consistency. Follow established conventions, use HTTP codes correctly, standardize your responses, and document everything. A well-designed API is a productivity multiplier for your entire team and your consumers. The patterns presented here are the same ones used by companies like Stripe, GitHub, and Twilio in their world-class APIs.



Comments (0)
Sign in to comment