Documenting an API in Laravel often involves using packages that rely on manual PHPDoc annotations. While this method works, it is time-consuming, error-prone, and often leads to documentation becoming outdated as the codebase evolves.
This disconnection between code and documentation can result in inaccuracies that mislead developers and hinder project maintenance.
There is also the option to maintain an OpenAPI spec file manually, but this too can become a ton of work and easily outdated.
Generating API documentation with static code analysis
On the other hand, there is the static code analysis approach.
Unlike traditional methods where documentation and code can diverge, static code analysis scans your codebase to infer types, parameters, and responses directly from the code. This means that as your code changes, your documentation changes with it, ensuring that the two are always in sync without requiring additional effort from developers.
For example, take a look at this simplified controller’s method code:
// app/Http/Controllers/Api/ExpensesController.php
public function index(Request $request)
{
$this->authorize('readAny', Expense::class);
$expenses = Expense::query()
->when(
$request->has('category'),
fn ($q) => $q->where('category', $request->enum('category', ExpenseCategory::class)),
)
->get();
return ExpenseResource::collection($expenses)->additional([
'can_create' => (bool) $request->user()->can('create', Expense::class),
]);
}
Just by looking at it you already know that:
this endpoint returns a successful response with a collection of expense resources with additional data showing if a user can create an expense
this endpoint may fail with 403 response when user is not authorized
user can pass status of the expense to the request which can be one of the available ExpenseStatus enum’s values
This is exactly what the static code analysis approach does: by scanning the codebase and inferring types, it can determine how requests and responses of the API look like, hence the generated documentation is always accurate and stays in sync with the codebase.
Meet Scramble
In this article I want to introduce you to Scramble: https://scramble.dedoc.co/
This is a package for Laravel that generates API documentation by relying on static code analysis. Using it, you won’t need to manually write and maintain PHPDoc annotations and will always have up-to-date API documentation which is in sync with your codebase.
Let’s install Scramble by simply requiring it in the Laravel API project:
composer require dedoc/scramble
Before we check the documentation, I want to briefly show you the API we’ll document:
GET /api/expenses
POST /api/expenses
This application is a simple expense tracker, allowing users to list all their expenses and also store an expense.
Now, let’s check the documentation. After you install Scramble, it is available at /docs/api
by default.
Documentation of GET /api/expenses endpoint
Here is the documentation for the GET /api/expenses
endpoint, whose code we saw in the prior example.
Just by analyzing the codebase, Scramble could:
Document the successful (200) response type from this endpoint.
Document the error (403) response when the user doesn’t have permission.
Document the error (401) response when the user is not authenticated.
Document the request’s status
parameter, noting that this parameter is an enum (including available values).
Ensuring the documentation is always up to date
Now imagine you make some changes to your endpoint: you want to add some new filters for expenses, return other additional data, or add a new possible status to the category enum.
// app/Http/Controllers/Api/ExpensessController.php
public function index(Request $request)
{
$this->authorize('readExpenses', Expense::class);
$expenses = Expense::query()
->when(
$request->has('category'),
fn ($q) => $q->where('category', $request->enum('category', ExpenseCategory::class)),
)
\+ ->when(
\+ $request->has('q'),
\+ fn (Builder $q) => $q->where(DB::raw('lower(title)'), 'like', '%'.Str::lower($request->get('q')).'%'),
\+ )
->get();
return ExpenseResource::collection($expenses)->additional([
'can_create' => (bool) $request->user()->can('createExpenses', Expense::class),
\+ 'can_update' => (bool) $request->user()->can('createExpenses', Expense::class),
]);
}
// app/Enums/ExpenseCategory.php
enum ExpenseCategory: string
{
case Travel = 'travel';
case Food = 'food';
\+ case Utilities = 'utilities';
}
And here is the resulting documentation:
The documentation of updated GET /api/expenses endpoint
As you can see, the documentation stays up to date with all the changes in the codebase. And you only changed your codebase, not the PHPDoc annotations.
PHPDoc annotations can still be useful, though! When you want to add a human description for properties or request parameters, you can still do it. Or when you know the type better than Scramble does, you can always take control.