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

Updated
featured-image.png

💡 I included the link to the example repository at the conclusion of this tutorial.

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'),
    ]);
}

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

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

    /**
     * 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

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.

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

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,
                ],
            ],
        ]);
    }
}

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

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' => [],
        ]);
    }
}

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(),
        ],
    ]);
}

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

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"),

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"),

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"
    *    )
    * )
 * )
 */

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

Fajarwz's photo Fajar Windhu Zulfikar

I'm a full-stack web developer who loves to share my software engineering journey and build software solutions to help businesses succeed.

Email me
Ads
  • Full-Stack Laravel: Forum Web App (Complete Guide 2024)
  • Flexible and powerful review system for Laravel, let any model review and be reviewed.

Share

Subscribe

Sign up for my email newsletter and never miss a beat in the world of web development. Stay up-to-date on the latest trends, techniques, and tools. Don't miss out on valuable insights. Subscribe now!

Comments

comments powered by Disqus