DEV Community

Fajar Windhu Zulfikar
Fajar Windhu Zulfikar

Posted on • Originally published at fajarwz.com on

Laravel Api Documentation Generator With OpenAPI/Swagger Using DarkaOnLine/L5-Swagger

Keyboard as API Documentation illustration

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 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 as a starter repo. So let's begin.

App Installation

An installation of the app can be found in the README.md of the repo.

Add A Seeder

Add a user to our users table to quickly test login endpoint

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

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

Run the Migration and seeder with the following command:

php artisan migrate:refresh --seed
Enter fullscreen mode Exit fullscreen mode

DarkaOnLine/L5-Swagger Installation

Step 1: Install the Package

Install the package with the following command:

composer require DarkaOnLine/L5-Swagger
Enter fullscreen mode Exit fullscreen mode

Step 2: Publish the Config File

Publish the config file of the package with the following command:

php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
Enter fullscreen mode Exit fullscreen mode

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

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;
}

Enter fullscreen mode Exit fullscreen mode

Insert Notations for AuthController.php

Register Method

    /**
     * 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
                ],
            ],
        ]);
    }
Enter fullscreen mode Exit fullscreen mode

To generate the documentation run the following command:

php artisan l5:generate
Enter fullscreen mode Exit fullscreen mode

Of course we need to run the app too

php artisan serve
Enter fullscreen mode Exit fullscreen mode

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

example={
    "name":"John",
    "email":"john@test.com",
    "password":"johnjohn1"
}
Enter fullscreen mode Exit fullscreen mode

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.

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

Login Method

/**
 * 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,
                ],
            ],
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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;
}
Enter fullscreen mode Exit fullscreen mode

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:

/**
 * 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' => [],
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

/**
 * 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(),
        ],
    ]);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And run the app

php artisan serve
Enter fullscreen mode Exit fullscreen mode

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

All API Docs

More type example of @oa\Property

If you want to upload files (image, pdf, etc.) just update like below

@OA\Property(property="current_cv", type="file"),
Enter fullscreen mode Exit fullscreen mode

We also can use predefined format="email" and even regexp pattern.

@OA\Property(property="email", type="string", pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$", format="email", example="myemail@gmail.com"),
Enter fullscreen mode Exit fullscreen mode

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

/**
 * @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"
    *    )
    * )
 * )
 */
Enter fullscreen mode Exit fullscreen mode

Conclusions

That’s it. We have successfully build an API documentation feature. Here we use 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.

Reference

Laravel API Documentation with Swagger Open Api and Passport

Read Also

Laravel Rest API Authentication Using JWT Tutorial

Top comments (0)