Laravel Api Documentation Generator With OpenAPI/Swagger Using DarkaOnLine/L5-Swagger
Tags: php, laravel, api, backend
Published: 2023-01-19 23:15:43

---
title: Laravel Api Documentation Generator With OpenAPI/Swagger Using DarkaOnLine/L5-Swagger
published: true
date: 2022-07-24 11:22:37 UTC
tags: 
- PHP
- Laravel
- API
- Backend
canonical_url: https://fajarwz.com/blog/laravel-api-documentation-generator-with-open-api-swagger-using-darkaonline-l5-swagger/
---

![Keyboard as API Documentation illustration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/taajbfqb7ddy8lr3739h.png)

In a team project, documentation become so important so everyone know what we have created, how we think, how it can solve our problem, how to use it, etc. 

Our Frontend and backend maybe communicate via API, a backend team create APIs and then frontend team create their app that can consume our API. But turns out they don't know how to use your team's API (you as a backend) and so you need to tell them how it works, how to use it. So you think maybe you need a documentation about the API.

If you work with Laravel and need an API Docs with OpenAPI Spesification, you can use this [DarkaOnLine/L5-Swagger](https://github.com/DarkaOnLine/L5-Swagger) package to generate your API documentation.

Here I will explain, step by step, about how to create Laravel API docs generator with OpenAPI/Swagger using DarkaOnLine/L5-Swagger. We will use this repo [fajarwz/blog-laravel-api-jwt](https://github.com/fajarwz/blog-laravel-api-jwt) as a starter repo. So let's begin.

## App Installation
An installation of the app can be found in the [`README.md`](https://github.com/fajarwz/blog-laravel-api-jwt/blob/master/README.md) of the repo.

### Add A Seeder
Add a user to our `users` table to quickly test `login` endpoint

```php
public function run()
{
    // \App\Models\User::factory(10)->create();

    \App\Models\User::factory()->create([
        'name' => 'User',
        'email' => 'user@test.com',
        'password' => bcrypt('useruser1'),
    ]);
}
```

Run the Migration and seeder with the following command:

```
php artisan migrate:refresh --seed
```

## DarkaOnLine/L5-Swagger Installation
### Step 1: Install the Package
Install the package with the following command:
```
composer require DarkaOnLine/L5-Swagger
```

### Step 2: Publish the Config File
Publish the config file of the package with the following command:
```
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
```

## Insert @OA\Info() Notation
First we need to insert general information of the API with `@OA\Info()` notation. We can insert it in the `/Http/Controllers/Controller.php` like so:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

/**
 * @OA\Info(
 *    title="My Cool API",
 *    description="An API of cool stuffs",
 *    version="1.0.0",
 * )
 */

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

```

## Insert Notations for AuthController.php
### Register Method
```php
    /**
     * Register
     * @OA\Post (
     *     path="/api/register",
     *     tags={"Auth"},
     *     @OA\RequestBody(
     *         @OA\MediaType(
     *             mediaType="application/json",
     *             @OA\Schema(
     *                 @OA\Property(
     *                      type="object",
     *                      @OA\Property(
     *                          property="name",
     *                          type="string"
     *                      ),
     *                      @OA\Property(
     *                          property="email",
     *                          type="string"
     *                      ),
     *                      @OA\Property(
     *                          property="password",
     *                          type="string"
     *                      )
     *                 ),
     *                 example={
     *                     "name":"John",
     *                     "email":"john@test.com",
     *                     "password":"johnjohn1"
     *                }
     *             )
     *         )
     *      ),
     *      @OA\Response(
     *          response=200,
     *          description="Success",
     *          @OA\JsonContent(
     *              @OA\Property(property="meta", type="object",
     *                  @OA\Property(property="code", type="number", example=200),
     *                  @OA\Property(property="status", type="string", example="success"),
     *                  @OA\Property(property="message", type="string", example=null),
     *              ),
     *              @OA\Property(property="data", type="object",
     *                  @OA\Property(property="user", type="object",
     *                      @OA\Property(property="id", type="number", example=1),
     *                      @OA\Property(property="name", type="string", example="John"),
     *                      @OA\Property(property="email", type="string", example="john@test.com"),
     *                      @OA\Property(property="email_verified_at", type="string", example=null),
     *                      @OA\Property(property="updated_at", type="string", example="2022-06-28 06:06:17"),
     *                      @OA\Property(property="created_at", type="string", example="2022-06-28 06:06:17"),
     *                  ),
     *                  @OA\Property(property="access_token", type="object",
     *                      @OA\Property(property="token", type="string", example="randomtokenasfhajskfhajf398rureuuhfdshk"),
     *                      @OA\Property(property="type", type="string", example="Bearer"),
     *                      @OA\Property(property="expires_in", type="number", example=3600),
     *                  ),
     *              ),
     *          )
     *      ),
     *      @OA\Response(
     *          response=422,
     *          description="Validation error",
     *          @OA\JsonContent(
     *              @OA\Property(property="meta", type="object",
     *                  @OA\Property(property="code", type="number", example=422),
     *                  @OA\Property(property="status", type="string", example="error"),
     *                  @OA\Property(property="message", type="object",
     *                      @OA\Property(property="email", type="array", collectionFormat="multi",
     *                        @OA\Items(
     *                          type="string",
     *                          example="The email has already been taken.",
     *                          )
     *                      ),
     *                  ),
     *              ),
     *              @OA\Property(property="data", type="object", example={}),
     *          )
     *      )
     * )
     */
    public function register(Request $request)
    {
        // validate the incoming request
        // set every field as required
        // set email field so it only accept the valid email format

        $this->validate($request, [
            'name' => 'required|string|min:2|max:255',
            'email' => 'required|string|email:rfc,dns|max:255|unique:users',
            'password' => 'required|string|min:6|max:255',
        ]);

        // if the request valid, create user

        $user = $this->user::create([
            'name' => $request['name'],
            'email' => $request['email'],
            'password' => bcrypt($request['password']),
        ]);

        // login the user immediately and generate the token
        $token = auth()->login($user);

        // return the response as json 
        return response()->json([
            'meta' => [
                'code' => 200,
                'status' => 'success',
                'message' => 'User created successfully!',
            ],
            'data' => [
                'user' => $user,
                'access_token' => [
                    'token' => $token,
                    'type' => 'Bearer',
                    'expires_in' => auth()->factory()->getTTL() * 60,    // get token expires in seconds
                ],
            ],
        ]);
    }
```

To generate the documentation run the following command:

```
php artisan l5:generate
```

Of course we need to run the app too

```
php artisan serve
```

Or if you don't want to generate manually every time you do some changes, you can add `L5_SWAGGER_GENERATE_ALWAYS=true` to `.env`, but that is not intended for production use.

The documentation can be accessed at `/api/documentation` or the full url `http://127.0.0.1:8000/api/documentation`.

So the above notation means our endpoint will be named as "Register". `@OA\Post` indicate that the HTTP method is `POST`. `path` for the api path / endpoint. `tags` for categorizing the endpoint. 

To draw a request body write `@OA\RequestBody`. To draw an object we can use `@OA\Property` with `type="object"`, for a string we can fill `type` with `"string"`. `property` is the name of our json object property / field. We can give an example request inside `@OA\Schema` and give the example

```php
example={
    "name":"John",
    "email":"john@test.com",
    "password":"johnjohn1"
}
```

To make a response use `@OA\Response`. We can create responses for multiple HTTP code using `response`.

To make an array we can fill the `type` with `"array"` and add `collectionFormat="multi"`, and use `@OA\Items` to generate every value of the array.

```php
@OA\Property(property="email", type="array", collectionFormat="multi",
    @OA\Items(
        type="string",
        example="The email has already been taken.",
    )
),
```

### Login Method
```php
/**
 * Login
 * @OA\Post (
 *     path="/api/login",
 *     tags={"Auth"},
 *     @OA\RequestBody(
 *         @OA\MediaType(
 *             mediaType="application/json",
 *             @OA\Schema(
 *                 @OA\Property(
 *                      type="object",
 *                      @OA\Property(
 *                          property="email",
 *                          type="string"
 *                      ),
 *                      @OA\Property(
 *                          property="password",
 *                          type="string"
 *                      )
 *                 ),
 *                 example={
 *                     "email":"user@test.com",
 *                     "password":"useruser1"
 *                }
 *             )
 *         )
 *      ),
 *      @OA\Response(
 *          response=200,
 *          description="Valid credentials",
 *          @OA\JsonContent(
 *              @OA\Property(property="meta", type="object",
 *                  @OA\Property(property="code", type="number", example=200),
 *                  @OA\Property(property="status", type="string", example="success"),
 *                  @OA\Property(property="message", type="string", example=null),
 *              ),
 *              @OA\Property(property="data", type="object",
 *                  @OA\Property(property="user", type="object",
 *                      @OA\Property(property="id", type="number", example=2),
 *                      @OA\Property(property="name", type="string", example="User"),
 *                      @OA\Property(property="email", type="string", example="user@test.com"),
 *                      @OA\Property(property="email_verified_at", type="string", example=null),
 *                      @OA\Property(property="updated_at", type="string", example="2022-06-28 06:06:17"),
 *                      @OA\Property(property="created_at", type="string", example="2022-06-28 06:06:17"),
 *                  ),
 *                  @OA\Property(property="access_token", type="object",
 *                      @OA\Property(property="token", type="string", example="randomtokenasfhajskfhajf398rureuuhfdshk"),
 *                      @OA\Property(property="type", type="string", example="Bearer"),
 *                      @OA\Property(property="expires_in", type="number", example=3600),
 *                  ),
 *              ),
 *          )
 *      ),
 *      @OA\Response(
 *          response=401,
 *          description="Invalid credentials",
 *          @OA\JsonContent(
 *              @OA\Property(property="meta", type="object",
 *                  @OA\Property(property="code", type="number", example=401),
 *                  @OA\Property(property="status", type="string", example="error"),
 *                  @OA\Property(property="message", type="string", example="Incorrect username or password!"),
 *              ),
 *              @OA\Property(property="data", type="object", example={}),
 *          )
 *      )
 * )
 */

public function login(Request $request)
{
    $this->validate($request, [
        'email' => 'required|string',
        'password' => 'required|string',
    ]);

    // attempt a login (validate the credentials provided)
    $token = auth()->attempt([
        'email' => $request->email,
        'password' => $request->password,
    ]);

    // if token successfully generated then display success response
    // if attempt failed then "unauthenticated" will be returned automatically
    if ($token)
    {
        return response()->json([
            'meta' => [
                'code' => 200,
                'status' => 'success',
                'message' => 'Login success.',
            ],
            'data' => [
                'user' => auth()->user(),
                'access_token' => [
                    'token' => $token,
                    'type' => 'Bearer',
                    'expires_in' => auth()->factory()->getTTL() * 60,
                ],
            ],
        ]);
    }
}
```

### Logout Method
If we want to make our method require authorization, we can insert `@OA\SecurityScheme` in our `app\Http\Controllers\Controller.php`. We can edit our `Controller.php` so it is like this:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

/**
 * @OA\Info(
 *    title="My Cool API",
 *    description="An API of cool stuffs",
 *    version="1.0.0",
 * ),
 * @OA\SecurityScheme(
 *     type="apiKey",
 *     in="header",
 *     securityScheme="token",
 *     name="Authorization"
 * )
 */

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
```

We use `type="apiKey"` and `name="Authorization"` so we can insert "Bearer xxxx" type token to the `Authorization` API. `type` is the type of the security scheme. Valid values are `basic`, `apiKey` or `oauth2`.

The in our `AuthController.php` we can insert the notations like so:

```php
/**
 * Logout
 * @OA\Post (
 *     path="/api/v1/logout",
 *     tags={"Auth"},
 *     @OA\RequestBody(
 *         @OA\MediaType(
 *             mediaType="application/json",
 *             @OA\Schema(
 *                 @OA\Property(
 *
 *                 ),
 *                 example={}
 *             )
 *         )
 *      ),
 *      @OA\Response(
 *          response=200,
 *          description="Success",
 *          @OA\JsonContent(
 *              @OA\Property(property="meta", type="object",
 *                  @OA\Property(property="code", type="number", example=200),
 *                  @OA\Property(property="status", type="string", example="success"),
 *                  @OA\Property(property="message", type="string", example="Successfully logged out"),
 *              ),
 *              @OA\Property(property="data", type="object", example={}),
 *          )
 *      ),
 *      @OA\Response(
 *          response=401,
 *          description="Invalid token",
 *          @OA\JsonContent(
 *              @OA\Property(property="meta", type="object",
 *                  @OA\Property(property="code", type="number", example=422),
 *                  @OA\Property(property="status", type="string", example="error"),
 *                  @OA\Property(property="message", type="string", example="Unauthenticated."),
 *              ),
 *              @OA\Property(property="data", type="object", example={}),
 *          )
 *      ),
 *      security={
 *         {"token": {}}
 *     }
 * )
 */
public function logout()
{
    // get token
    $token = JWTAuth::getToken();

    // invalidate token
    $invalidate = JWTAuth::invalidate($token);

    if($invalidate) {
        return response()->json([
            'meta' => [
                'code' => 200,
                'status' => 'success',
                'message' => 'Successfully logged out',
            ],
            'data' => [],
        ]);
    }
}
```

We use our security scheme in the `logout` method by adding a security key to the annotation, so this endpoint require us to authorize first by clicking "authorize" button and insert the auth token before we can successfully try the endpoint.

### Me method
Insert the following code in `me()` method in `app\Http\Controllers\Api\UserController.php`:

```php
/**
 * Me
 * @OA\Get (
 *     path="/api/me",
 *     tags={"User"},
 *     @OA\RequestBody(
 *         @OA\MediaType(
 *             mediaType="application/json",
 *             @OA\Schema(
 *                 @OA\Property(
 *
 *                 ),
 *                 example={}
 *             )
 *         )
 *      ),
 *      @OA\Response(
 *          response=200,
 *          description="Success",
 *          @OA\JsonContent(
 *              @OA\Property(property="meta", type="object",
 *                  @OA\Property(property="code", type="number", example=200),
 *                  @OA\Property(property="status", type="string", example="success"),
 *                  @OA\Property(property="message", type="string", example="User fetched successfully!"),
 *              ),
 *              @OA\Property(property="data", type="object", 
 *                  @OA\Property(property="user", type="object",
 *                      @OA\Property(property="id", type="number", example=2),
 *                      @OA\Property(property="name", type="string", example="User"),
 *                      @OA\Property(property="email", type="string", example="user@test.com"),
 *                      @OA\Property(property="email_verified_at", type="string", example=null),
 *                      @OA\Property(property="updated_at", type="string", example="2022-06-28 06:06:17"),
 *                      @OA\Property(property="created_at", type="string", example="2022-06-28 06:06:17"),
 *                  ),
 *              ),
 *          )
 *      ),
 *      @OA\Response(
 *          response=401,
 *          description="Invalid token",
 *          @OA\JsonContent(
 *              @OA\Property(property="meta", type="object",
 *                  @OA\Property(property="code", type="number", example=422),
 *                  @OA\Property(property="status", type="string", example="error"),
 *                  @OA\Property(property="message", type="string", example="Unauthenticated."),
 *              ),
 *              @OA\Property(property="data", type="object", example={}),
 *          )
 *      ),
 *      security={
 *         {"token": {}}
 *     }
 * )
 */
public function me() 
{
    // use auth()->user() to get authenticated user data

    return response()->json([
        'meta' => [
            'code' => 200,
            'status' => 'success',
            'message' => 'User fetched successfully!',
        ],
        'data' => [
            'user' => auth()->user(),
        ],
    ]);
}
```

Again we use `security` key with value `token` so we can insert "Bearer xxxx" token.

If you dont' use the auto generation feature, manually run again the docs generator

```
php artisan l5-swagger:generate
```

And run the app

```
php artisan serve
```

We can see the generation result in `/api/documentation` like this

![All API Docs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oanbwr9uu4ifmrph330x.png)

## More type example of @OA\Property
If you want to upload files (image, pdf, etc.) just update like below
```php
@OA\Property(property="current_cv", type="file"),
```

We also can use predefined format="email" and even regexp pattern.
```php
@OA\Property(property="email", type="string", pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$", format="email", example="myemail@gmail.com"),
```

If we need to insert parameter in URL we can use @OA\Parameter

```php
/**
 * @OA\Get(
    * path="/api/users/{userId}",
    * operationId="GetUserDetails",
    * tags={"UserDetails"},
    * security={ {"bearer": {} }},
    * @OA\Parameter(
    *    description="User ID",
    *    in="path",
    *    name="userId",
    *    required=true,
    *    example="1",
    *    @OA\Schema(
    *       type="integer",
    *       format="int64"
    *    )
    * )
 * )
 */
```

## Conclusions
That’s it. We have successfully build an API documentation feature. Here we use [DarkaOnLine/L5-Swagger](https://github.com/DarkaOnLine/L5-Swagger) package, learn how to use it, integrate it with our controllers, and done, we have successfully implemented it.

A repo for this example case can be found here [fajarwz/blog-laravel-api-docs-l5swagger](https://github.com/fajarwz/blog-laravel-api-docs-l5swagger).

## Reference
[Laravel API Documentation with Swagger Open Api and Passport](https://codingdriver.com/laravel-api-documentation-with-swagger-open-api-and-passport.html)

## Read Also
[Laravel Rest API Authentication Using JWT Tutorial](https://fajarwz.com/blog/laravel-rest-api-authentication-using-jwt-tutorial/)