An endpoint nobody knows how to call doesn’t exist. You can have the cleanest controller in the world, and if a consumer has to read it to find out which fields are required, you have shipped a library with the source as its manual.
Chapter 14 was about the people who work on the code. This chapter is about the people who work against it: the developers on the other side of the API, who will never see a line of your PHP and shouldn’t have to.
What a Consumer Needs
Sit where your consumer sits. They have a task, a deadline, and a token somebody sent them. What they need from you, in the order they need it:
- A first successful call. One request they can paste into a terminal and see work.
- How to authenticate. Where the token goes, what an expired one looks like.
- Every endpoint, with a real request and a real response.
- Every error they might get, and what to do about each.
- The limits. Rate limits, page sizes, how long an idempotency key lives.
- What will change, and when. The versioning policy and a dated changelog.
Most API documentation covers the third item and stops. The first is the one that decides whether a consumer’s afternoon goes well.
curl https://licenses.example.com/api/licenses \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
Three lines at the top of the page. If they work, the consumer trusts everything below them. If they don’t, nothing else you wrote will be read.
Describe the API in a Format Machines Read
The industry’s answer to “how do I describe an HTTP API?” is OpenAPI: a JSON or YAML document that lists every path, every parameter, every response, and the shape of each.
A fragment, for one endpoint of this API:
/licenses:
post:
security:
- bearer: []
requestBody:
content:
application/json:
schema:
required: [name, domain]
properties:
name: { type: string, maxLength: 100 }
domain: { type: string, maxLength: 100 }
responses:
'201': { description: The created license }
'422': { description: Validation failed }
One document like that gives you several things at once: a browsable reference site, request collections for tools like Postman, generated client libraries, and a file that other tools can compare from one release to the next. That last use is the one this chapter cares most about.
Generate It, Don’t Write It
You could write that YAML by hand. For a dozen endpoints it would take an afternoon. And then it would have the problem Chapter 14 described: accurate on the day you wrote it, wrong on some later day, and nobody could tell you which.
Look again at the fragment. required: [name, domain]. maxLength: 100. You have already written those facts, in StoreLicenseRequest:
'name' => ['required', 'string', 'max:100'],
'domain' => ['required', 'string', 'max:100'],
And the 201 response is LicenseResource. The description of the API already exists, in the two places this book told you to put it. A hand-written OpenAPI file is a second copy of information you have, and a second copy drifts.
So generate it from the code. The Laravel community has two established packages for this. Scramble reads your routes, FormRequests, and Resources, and produces an OpenAPI document without annotations in your code. Scribe does the same job with a different approach and can also call your endpoints to capture example responses. Both are worth an afternoon’s evaluation. I’ll use Scramble’s behavior as the example, as it stands at the time of writing:
composer require dedoc/scramble
With that installed, the application serves a reference site at /docs/api and the document itself at /docs/api.json, covering every route under api. Outside your local environment the pages are closed by default, behind a Gate named viewApiDocs that you define. Don’t take a package’s word for that, or mine: after you deploy, request both URLs from outside and see what comes back. (Laravel documentation: Authorization › Gates.)
Why This Works for This API
A generator can only read what is declared. That is the payoff of everything in Chapters 2 and 5.
- Validation lives in FormRequests, so the generator knows every field, its type, and its limits. (Laravel documentation: Validation › Form Request Validation.)
- Output goes through Resources, so it knows the response shape. (Laravel documentation: Eloquent: API Resources.)
- Routes are resourceful, so paths and verbs follow a pattern it recognizes.
- Authentication is
auth:sanctum, so it knows which routes need a token.
An API that validates inline with hand-written if statements and returns arrays assembled in the controller gives a generator nothing to read. An earlier version of this API, with its own response classes wrapped around everything, would have needed an annotation on every method to explain what the framework could no longer see.
This is the same argument as Chapter 14, about people: stay close to the framework and a newcomer already knows your conventions. Tools are newcomers too.
What You Still Write
Generation gives you structure. It can’t give you meaning. The words are yours:
- A sentence on each endpoint saying what it is for, not what it does. “Queues the license for deletion at the provider. The license may remain visible in the list for a short time.”
- The reason behind a rule a consumer might trip on. Why a domain is limited to 100 characters is less interesting than why a delete answers 202.
- The guides: getting started, authentication, retrying safely, pagination, versioning.
Generators read docblocks on controller methods and descriptions on rules, so most of this lives in the code, beside what it describes, and changes in the same pull request.