What you'll learn
Design an API that clients can predict. Resource URLs, HTTP semantics, stable representations, validation errors, pagination metadata, and idempotency form a public contract that outlives any controller implementation.
By the end of this lesson, you'll be able to:
- Design resource-oriented endpoints
- Return consistent success and error documents
- Apply validation, status codes, pagination, and idempotency
Core mental model
Professional PHP is less about memorizing APIs and more about choosing a clear boundary for each responsibility. Use this table as a decision guide while reading the examples.
| Concept | What it protects | Decision rule |
|---|---|---|
| Resource | Stable API vocabulary | Model nouns and manipulate them with HTTP methods. |
| Representation | Client-facing data shape | Serialize deliberately; never expose tables directly. |
| Idempotency | Safe retries | A repeated request should not duplicate a completed effect. |
Professional workflow
Build the feature in small, verifiable steps. Each step leaves the system in a state you can test.
- Describe the JSON API contract boundary: its inputs, outputs, invariants, and expected failures.
- Implement the smallest happy path behind an explicit contract.
- Add validation and translate low-level failures into language the caller understands.
- Exercise the boundary with realistic data, then inspect output, logs, and resource cleanup.
- Refactor only after behavior is protected by a repeatable check.
Make the boundary visible
Guided code lab
Return a resource envelope
The serializer controls public fields and the response includes a useful Location header.
<?php
$course = $createCourse->handle(CreateCourseData::fromArray($request->json()));
return Response::json([
'data' => [
'id' => $course->id,
'type' => 'course',
'attributes' => ['title' => $course->title, 'status' => $course->status->value],
],
], status: 201, headers: ['Location' => "/api/courses/{$course->id}"]);Make validation errors actionable
A stable code serves software clients; field details support forms without leaking internals.
<?php
return Response::json([
'error' => [
'code' => 'validation_failed',
'message' => 'The request contains invalid fields.',
'fields' => [
'title' => ['The title is required.'],
'price' => ['The price must be a non-negative integer.'],
],
'request_id' => $request->id(),
],
], status: 422);Production practice
Contract
Version behavior intentionally and document examples, status codes, authentication, limits, and error codes.
Verification
Use contract tests for response schemas and feature tests for authorization, retries, malformed JSON, and limits.
Operations
Track latency and error rate by route; apply request IDs, rate limits, payload limits, and deprecation windows.
Common failure mode
Independent workshop
Create a versioned course API with list, create, partial update, delete, filtering, cursor pagination, and a duplicate-safe enrollment command.
Your finished workshop must include:
- OpenAPI-style endpoint document
- Stable serializer and error format
- Idempotency and authorization tests
Definition of done
Recap & quick check
Key takeaways
- An API is a long-lived contract.
- HTTP methods and status codes communicate semantics.
- Serializers protect the public representation.
- Idempotency makes network retries safe.
Quick check
1. Which status fits successful creation?
2. Why use a serializer?
3. What does idempotency prevent on retry?
Keep the workshop: later phases deliberately build on these boundaries, so today's small example can become part of your portfolio architecture.