Build, Test and Document REST APIs in PHP & Laravel — Part III — Documenting

Santhosh Krishnamoorthy
7 min readNov 5, 2021

--

Co-authored by Jotheeswaran Gnanasekaran, Swati Akkishetti, Charan Sai, Harsha Sudheer and Regina Sharon G

Welcome to Part-III of the series on Building, Testing and Documenting REST APIs in PHP+Laravel.

PHP+Laravel CRUD API — Build, Test and Document

If you haven’t gone through the first two instalments, I highly recommend that you do so.

This instalment will focus on generating documentation for the REST APIs that we have built and tested in the earlier two articles.

Documenting the APIs is a highly important, but often overlooked part of the development process. It becomes all the more imperative when the developed APIs are going to be used by a different team for integrating into something they are building perhaps, which is a very common scenario in a microservices kind of environment.

Let us quickly dig in and see how this can be achieved in our PHP+Laravel project using Swagger.

Swagger is a golden standard when it comes to documenting RESTful APIs. Though, Swagger is much more than just a documentation tool, in this article, we will primarily focus on the document generation feature of it. You can read more about Swagger here — https://swagger.io/

Install & Configure Swagger :

The Laravel package that we need for this is — DarkaOnLine/L5-Swagger’

Let us install this into our Laravel project.

If you have followed Part-I and Part-II of the series, you should already have a project setup. If not, would highly recommend that you go through those articles before beginning, since the APIs that we build in those articles are the ones we will be documenting here.

Open a terminal, switch to your project directory and run this command :

composer require darkaonline/l5-swagger

With the package installed, let us publish the swagger config. Can be done using this command :

php artisan vendor:publish — provider “L5Swagger\L5SwaggerServiceProvider”

This should create a file named ‘l5-swagger.php’ under the ‘config’ directory of your project.

Some of the useful values in that file that you can change would be :

'route' =>  [
/*
* Route for accessing api documentation interface
*/
'api' => 'api/documentation',
],

‘api’ — represents the URL that you will use to access the documentation. Let us change this to be like /api/docs

'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false),

‘generate_always’ — If you would like the docs to be generated automatically every time you make a change, you can change this value to ‘true’. This is better in a DEV environment, surely not on production.

Generate the Docs :

Now, let us try to generate the docs by running this command :

php artisan l5-swagger:generate

When you execute this, you should see an error like so :

Error when you run swagger generate very first time without any annotations

This is expected, as we haven’t yet added any annotations to our code for Swagger to pick up. Let us start doing that.

Annotations :

Open up the Base Controller — app/Http/Controllers/Controller.php and add these lines just above the beginning of the class definition.

/*** @OA\Info(*    title="BookShop API",*    version="1.0.0",* )*/

Some basic annotation to inform Swagger of what the APIs are about. Now, run the generate-docs command as before to verify that things are fine.

Annotate the Endpoints:

Next, let us add annotations to the different API Endpoints which in our case would be the route methods in the respective Controller.

Let us start with the ‘GET’ API.

Add these lines above the ‘getBook’ method in the ‘app/Http/Controllers/BookController.php file :

/*** @OA\Get(*    path="/books/{bookId}",*    operationId="getBook",*    tags={"getBook"},*    summary="Get details of a Book",*    description="Returns details of a Book",*    @OA\Parameter(*        name="bookId",*        description="Book Id",*        required=true,*        in="path",*        @OA\Schema(*            type="number"*        )*      ),*    @OA\Response(*        response=200,*        description="successful operation",*        @OA\JsonContent(*             type="object",*             @OA\Property(*               property="title",*               type="string",*               example="Hakuna Matata"*             ),*             @OA\Property(*               property="price",*               type="number",*               format="integer",*               example=200*             ),*             @OA\Property(*                property="author",*                type="object",*                example={*                  "first_name": "John",*                  "last_name": "Brown",*                  "email":"babybuffalo@gmail.com"*                },*                      @OA\Property(*                         property="first_name",*                         type="string",*                         example="John"*                      ),*                      @OA\Property(*                         property="last_name",*                         type="string",*                         example="Brown"*                      ),*                      @OA\Property(*                         property="email",*                         type="string",*                         example="babybuffalo@gmail.com"*                      ),*             ),*        ),*     ),*    @OA\Response(response=400, description="Bad request"),*    )** Returns all the details for a given Book id*/

These annotations define the input requirements for the GET api along with the JSON structure of the Response.

A quick look at the annotations:

@OA — represents Open API specification. Read more here

@OA\Get — Indicates the HTTP method i.e GET in this case. This will have many attributes associated with it, like :

‘path’ — URL for the endpoint

‘tags’ — helpful for grouping the APIs by sections

‘description’ — some brief information about the API being referred to here

@OA\Parameter — for the GET api in our case, we need to pass in the bookID. This annotation defines this input parameter, its ‘type’ etc.

Then, comes the Response

@OA\Response — You can have multiple Responses to an API. We add one here for a successful response.

@OA\JsonContent — Defines the structure of the JSON being returned by the GET API. This JSON has multiple property values of various types as you see in the above annotation.

These properties are annotated by using :

@OA\PropertyEach property has a ‘key’ which is the name and a ‘type’. There are different type values like string, number, integer, boolean, array and object.

The GET api response, the JSON content that is sent back has an ‘object’ which has ‘title’ and ‘price’ fields and another embedded object, ‘author’ that has details about the Author.

Generate Docs again:

Once the annotations are added, we can generate the documentation by running :

php artisan l5-swagger:generate

Swagger UI:

Now, visit the URL that you configured in ‘config/l5-swagger.php’, in our case that is — http://127.0.0.1:8000/api/docs

You should see something like this :

Expand the section and you see all the details regarding the BookShop GET API endpoint, like so :

There is also a facility on the UI to quickly invoke and test out the API.

Awesome, the documentation for GET is now ready!

Following a similar process you can add annotations to describe the other API endpoints (the POST, PUT and DELETE ).

Add these lines above the ‘addBook’ method (POST API) in the ‘app/Http/Controllers/BookController.php file :

/*** @OA\POST(*     path="/books",*     summary="Add a new Book",*     tags={"addBook"},*     @OA\RequestBody(*        required = true,*        description = "New Book Details",*        @OA\JsonContent(*             type="object",*             @OA\Property(*                property="title",*                type="string",*                example="Davinci Code"*             ),*             @OA\Property(*                property="price",*                type="number",*                format="integer",*                example=550*             ),*             @OA\Property(*                property="author",*                type="object",*                example={*                  "first_name": "Dan",*                  "last_name": "Brown",*                  "email": "dan@danbrown.com"*                },*                      @OA\Property(*                         property="first_name",*                         type="string",*                         example="Dan"*                      ),*                      @OA\Property(*                         property="last_name",*                         type="string",*                         example="Brown"*                      ),*                      @OA\Property(*                         property="email",*                         type="string",*                         example="dan@danbrown.com"*                      ),*             ),*        ),*     ),**    @OA\Response(*        response=201,*        description="successful operation",*        @OA\JsonContent(*             type="string",*             example="Book is successfully added"*        ),*     ),** )*/

Add these lines above the ‘updateBook’ method (PUT API) in the ‘app/Http/Controllers/BookController.php file :

/*** @OA\PUT(*     path="/books/{bookId}",*     summary="Update a Book",*     tags={"updateBook"},*    @OA\Parameter(*        name="bookId",*        description="Book Id",*        required=true,*        in="path",*        @OA\Schema(*            type="number"*        )*      ),*     @OA\RequestBody(*        required = true,*        description = "Update Book Details",*        @OA\JsonContent(*             type="object",*             @OA\Property(*                property="title",*                type="string",*                example="The Elephant Whisperer"*             ),*             @OA\Property(*                property="price",*                type="number",*                format="integer",*                example=850*             ),*        ),*     ),*    @OA\Response(*        response=200,*        description="successful operation",*        @OA\JsonContent(*             type="string",*             example="Book is successfully updated"*        ),*     ),* )*/

Add these lines above the ‘deleteBook’ method (DELETE API) in the ‘app/Http/Controllers/BookController.php’ file :

/*** @OA\Delete(*    path="/books/{bookId}",*    operationId="deleteBook",*    tags={"deleteBook"},*    summary="Delete a Book",*    description="Deletes a Book",*    @OA\Parameter(*        name="bookId",*        description="Book Id",*        required=true,*        in="path",*        @OA\Schema(*            type="number"*        )*      ),*    @OA\Response(*        response=200,*        description="successful operation",*        @OA\JsonContent(*             type="string",*             example="Book is successfully deleted"*        ),*     ),*    @OA\Response(response=404, description="Book not found"),*    )** Returns all the details for a given Book id*/

After these changes, regenerate the API docs by running this command :

php artisan l5-swagger:generate

Bring up the Swagger UI again or refresh the page if you are already on it.

You should see something like this :

Congratulations! With this, you have successfully documented all your REST APIs, a crucial part of the development process.

You can find all the code that you worked through in this 3 part series on GitHub:

Note: The code here is only for educational and learning purposes, definitely not production ready.

--

--

Santhosh Krishnamoorthy

Passionate Technologist. Also, a Naturalist and a Nature Photographer. Find my Wildlife & Nature Photography blog @ framesofnature.com