How To Generate a OpenAPI/Swagger Spec for your Laravel API
You’re investing in your API, and that means finally creating an OpenAPI spec that accurately describes your API. Of course you could write the document by hand, or use a GUI tool to make it easier. Or with just a bit of upfront work, you can generate a complete OpenAPI specification directly from your Laravel application.
Back in the time of Swagger documents, BeyondCode had a well known package for performing spec generation. However, with the advent of OpenAPI, a new package, Scribe
An Overview of Scribe
Scribe is a robust documentation solution for PHP APIs. It helps you generate comprehensive human-readable documentation from your Laravel/Lumen/Dingo codebase. That includes HTML documentation, code samples, Postman Collections, and most importantly in our case, OpenAPI specifications.
So let’s start by installing the package using composer, and explore the options available.
Once installed, we want to publish the package configuration so that we can make any changes in how we want this to work.
Let’s take a quick look at the specific configuration options that will help us optimize this package to work with our Laravel API:
routes
- this option allows us to configure how we want to detect the API routes, and prefix we may use and any routes we want to exclude by default.type
- we can choose betweenstatic
(a static HTMI page) andlaravel
(a Blade view). We will get into more details on the differences later.openapi
**- **this section allows you to toggle OpenAPI generation on or off. We’ll toggle it on.auth
- specify the API’s authentication mechanism. This will be used to describe thesecurity
section of the OpenAPI documentstrategies
- this is where we configure how Scribe will interact with our application to get the data needed to create the specification and documentation.
Scribe’s Default OpenAPI Output
As mentioned above, the type
config allows us to specify the type of output we get, and also where we want it to be outputted. The static
option will generate HTMI documentation pages within our public
directory. laravel
we will generate this within the storage
directory.
Note, anything in the storage
directory typically won’t be committed to version control - so you would need to update the .gitignore
file if you want to version this. For this article, we will keep the defaults as we want to focus on the OpenAPI Specification not the documentation.
Testing Generation
When we first install and set up this package, we should run a test to make sure that everything is configured correctly and we aren’t going to run into issues further on.
You should see a console output similar to the following:
So, we can confirm that our package is working correctly with our application, let’s take a look at the OpenAPI Specification that was generated and see what changes are required.
This is our default setup in Laravel - we are using a project I have yet to add an API to. Let’s add some endpoints so we can see something a little more fleshed out.
Our Example App: The Standup API
We’ll be working on an asynchronous stand-up application, it allows you to do your daily check-ins on one system, enables your manager to have a high level overview of team blockers and mood etc. You can follow along with the GitHub repository here
Non-Optimized Example Output
Now we got that out of the way, let’s regenerate our OpenAPI Specification now that I have added BREAD (Browse, Read, Edit, Add, Delete) endpoints.
That was a lot to look through, so let’s do a run through of each path - so we can understand what all this YAML actually means.
Documenting all Response Codes
Let’s focus on the browse endpoint, accessed through /api/standups
, which returns a collection of stand-ups that are part of the department you belong to.
You may have noticed that by default Scribe is only documenting the error responses of the API; it’s missing how the API would respond successfully.
How Scribe Works
This is a good opportunity to explain about how Scribe works under the hood. Scribe scans your application routes to identify which endpoints should be documented based on your configuration. It then extracts metadata from your routes, such as route names, URI patterns, HTTP methods, and any specific annotations or comments in the controller that might be relevant for documentation.
Scribe then uses the extracted metadata to perform request simulation on your API. It captures the responses that come back, including: status codes, headers, and body content. All this then get packaged into an internal representation of your API, which is how the OpenAPI spec is created.
In the example above, only the 401
is being documented because Scribe hasn’t been configured with the proper authentication information, which makes it unable to access the proper response.
Getting to 200
Let’s modify our Laravel code to get some useful information about our 200
responses.
To achieve this we will use PHP 8.0 Attributes to add additional information to our controllers, this will use the built in Laravel ecosystem to make a request, inspect the information, and write the specification for you. Let’s have a look at our controller:
Adding tags
In OpenAPI, tags
are used to group related operations together. Typically, a good way to use tags is to have one tag per “resource” and then associate all the relevant operations that access and modify that resourc together. We’ll add a group annotation to the top of the controller.
Authenicate Scribe
Let’s next focus on the invoke
method that is what will be used to generate the path information. We use #[Authenticated]
to let Scribe know that this endpoint needs to be authenticated
Add Descriptions
Use #[Endpoint]
to add additional information about this endpoint; describing what it’s function is.
Adding Responses
Finally, we want to add #[ResponseFromApiResource]
to let Scribe know how this API should respond, passing through the resource class and the model itself so Scribe can make a request in the background and inspect the types on the response payload. Also, we pass the boolean flag for whether or not this response should return a collection or not.
Now let’s see the OpenAPI spec:
Documenting Parameters
So far so good! However, this API example is limited. What if we add query parameters like filtering and sorting which we would likely want on a real API.
In terms of Laravel implementation, we recommend use the spatie/laravel-query-builder
package to enable JSON:API style filtering on my API, as it ties directly into Eloquent ORM from the request parameters. Let’s start adding some filters.
Our controller code used our StandUpRepository
which just leverages Eloquent to query our database through a shared abstraction. However, we want to lean on the package by Spatie, which has a slightly different approach. Let’s rewrite this code to make it more flexible.
We use the QueryBuilder
class from the package, to pass in the result of our repository call. The repository is just passing a pre-built query back, which we can use to paginate or extend as required. I prefer this approach as the sometimes you want to tie multiple methods together. You will see that I have 4 new methods that weren’t there before:
allowedFilters
allowedIncludes
allowedSorts
getEloquentBuilder
The first three allow you to programmatically control what parts of the query parameters to use and which to ignore. The final one is to get back the eloquent query builder, that we want to use as we know the API for it. The package returns a custom query builder, which does not have all of the methods we may want. Let’s flesh out the filter, include, and sort method calls next.
Going back we add attributes that will be parsed - so that our OpenAPI spec is generated with all available options:
Note
You may have noticed that the syntax has collapsed all the metadata into one attribute. It’s just a code style choice, there’s no change in functionality.
The result of the above will be the following inside your OpenAPI specification:
Quite convenient I am sure you can agree!
A More Complex Endpoint
Let’s now move onto documenting our store
endpoint which is what is used to create a new stand-up.
For the most part, this has been documented quite well by leaning on the Laravel framework and understanding what the validation rules on the request means. Let’s enhance this by adding some information.
This is similar to what we did on the IndexController
but this time we are jumping straight into grouping the attributes all together at the top of the class. We do not need to add these above the invoke
method, as this class only performs the one action anyway. I would consider moving these if I were to leverage additional Attributes for different purposes on the method, however for now I am not. Let’s now regenerate the OpenAPI Specification to see what the difference is, but this time I am going to omit the request validation information.
As you can see, the information is starting to build up based on the information we pass through to the PHP Attributes. Let’s start expanding on the request body and response information and build a better specification.
Now we have the body parameters for this request, as well as how the API is expected to respond. We are currently only documenting the happy path - as we have yet to decide how we want to handle errors. This will create the following in your OpenAPI Specification:
As you can see, a lot more information is provided which will help anyone who wants to interact with this API.
Summary
If we follow this approach throughout our API, we can generate a well documented OpenAPI Specification for our Laravel based API - utilizing modern PHP to add information to our code base. This not only aids in the OpenAPI generation, but it also adds a level of in-code documentation that will help onboard any new developer who needs to know what the purpose of an endpoint may be.
Last updated on