Developing REST API for Web Application
REST (Representational State Transfer)—an architectural style for APIs based on HTTP. De facto standard for web applications, founded on resources, HTTP methods, and status codes. A well-designed REST API is predictable, cacheable, and scales horizontally.
Key Principles
Resource-oriented: URL identifies the resource, HTTP method defines the action:
GET /api/v1/articles — list articles
POST /api/v1/articles — create article
GET /api/v1/articles/42 — article with id=42
PUT /api/v1/articles/42 — full update
PATCH /api/v1/articles/42 — partial update
DELETE /api/v1/articles/42 — delete
GET /api/v1/articles/42/comments — article comments
Stateless: each request contains all information needed for its processing. No server session state between requests.
Uniform responses:
// Success
{
"data": { "id": 42, "title": "..." },
"meta": { "total": 1 }
}
// List
{
"data": [...],
"meta": { "total": 150, "page": 1, "per_page": 20 }
}
// Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Title field is required",
"details": [{ "field": "title", "message": "required" }]
}
}
HTTP Status Codes
Proper use of codes is an important part of the contract:
| Code | Situation |
|---|---|
| 200 OK | Successful GET, PUT, PATCH |
| 201 Created | Successful POST, resource created |
| 204 No Content | Successful DELETE |
| 400 Bad Request | Validation error |
| 401 Unauthorized | Missing or invalid token |
| 403 Forbidden | No permission (token valid) |
| 404 Not Found | Resource not found |
| 409 Conflict | Conflict (duplicate email) |
| 422 Unprocessable Entity | Semantic error |
| 429 Too Many Requests | Rate limit exceeded |
| 500 Internal Server Error | Unexpected server error |
Pagination
Three approaches:
Offset-based (page + per_page):
GET /api/articles?page=2&per_page=20
Simple, but slower on large volumes due to OFFSET N in SQL.
Cursor-based (after + limit):
GET /api/articles?after=eyJpZCI6NDJ9&limit=20
Cursor is encrypted value of last record. Fast at any volume, but can't "jump" to page N.
Keyset (after_id + limit): simplified cursor via first ID:
GET /api/articles?after_id=42&limit=20
Filtering and Sorting
GET /api/articles?status=published&author_id=5&created_after=2024-01-01
GET /api/articles?sort=created_at&order=desc&fields=id,title,slug
Including related data (include):
GET /api/articles/42?include=author,tags,comments_count
Versioning
URL versioning—most explicit approach:
/api/v1/articles
/api/v2/articles — breaking change
Alternative—Accept header: Accept: application/vnd.app.v2+json.
Implementation Example (Laravel)
// routes/api.php
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
Route::apiResource('articles', ArticleController::class);
Route::get('articles/{article}/comments', [CommentController::class, 'index']);
});
// ArticleController
public function index(IndexArticleRequest $request)
{
$articles = Article::query()
->when($request->status, fn($q, $v) => $q->where('status', $v))
->with($request->include ?? [])
->paginate($request->per_page ?? 20);
return ArticleResource::collection($articles);
}
Timelines
REST API for CRUD application (10–20 endpoints, auth, validation, docs): 1–2 weeks.







