12 Aug 2019
cat: API, Laravel, PHP
2 Comments

Lumen API Tutorial – Documentation using Swagger UI

Introduction

In this tutorial we will use Swagger UI to create documentation for our Lumen or Laravel API. We will create a command that generates the swagger JSON file and a page to render the documentation. Then we will write basic phpdoc blocks and annotations.

Installation

We will use this specific package which provides us the methods we need to create the swagger JSON file.

https://github.com/zircote/swagger-php

To install we just use composer

composer require zircote/swagger-php

Artisan Command

Now we can write an artisan command which scans a specific directory for phpdoc blocks with annotations about our API.

Create a file app/Console/Commands/SwaggerScan.php.

namespace App\Console\Commands;

use Illuminate\Console\Command;

class SwaggerScan extends Command
{
    protected $signature = 'swg:scan';

    public function handle()
    {
        $path = dirname(dirname(__DIR__));
        $outputPath = dirname(dirname(dirname(__DIR__))) . DIRECTORY_SEPARATOR . 'public/swagger.json';
        $this->info('Scanning ' . $path);

        $openApi = \OpenApi\scan($path);
        header('Content-Type: application/json');
        file_put_contents($outputPath, $openApi->toJson());
        $this->info('Output ' . $outputPath);
    }
}

This command will generate a swagger.json file at the public directory.

Swagger UI Page

We need to create a page to view the documentation. On Lumen we create a file public/swagger-ui.html.

If you are using Laravel you can also use the blade templating engine.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Api Documentation</title>
    <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-standalone-preset.js"></script>
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<script>
    (function () {
        SwaggerUIBundle({
            url: "swagger.json",
            dom_id: '#swagger-ui',
            presets: [
                SwaggerUIBundle.presets.apis,
                SwaggerUIStandalonePreset
            ],
            layout: "StandaloneLayout"
        });
    })();
</script>
</body>
</html>

The javascript in the bottom parses the swagger.json file and renders it on theĀ div with the ID “swagger-ui”.

Annotations

Now, what’s left to do is to write some annotations, run the artisan command, and view the documentation on the browser.

Technically we can write the annotations anywhere as long as it is inside the directory that we specify on the artisan command but in this tutorial I will use the controller, view-models, and transformers.

Sometimes request body looks different from the response so we can use view-models or just the eloquent model for the request body. For the response we write it on the response transformers. Then finally for the details of the request like the method (GET, POST, PUT, PATCH, and DELETE) we put it in the controller.

The first annotations we need to write is the basic informationabout our API.

Edit your app/http/Controllers/Controller.php.

/**
 * Class Controller
 * @package App\Http\Controllers
 * @OA\OpenApi(
 *     @OA\Info(
 *         version="1.0.0",
 *         title="Laravel Swagger Tutorial",
 *         @OA\License(name="MIT")
 *     ),
 *     @OA\Server(
 *         description="API server",
 *         url="http://api.laravel-swagger-tutorial.test/",
 *     ),
 * )
 */
class Controller extends BaseController
{
    // omitted for brevity
}
Here are some example schemas/formats that we can reference to other annotations.

NOTE: Using view-models is not required.

app/ViewModels/PostViewModel.php

namespace App\ViewModels;

/**
 * Class PostViewModel
 * @property string $title
 * @property string $body
 * @package App\ViewModels
 * @OA\Schema(
 *     schema="PostRequest",
 *     type="object",
 *     title="PostRequest",
 *     required={"title", "body"},
 *     properties={
 *         @OA\Property(property="title", type="string"),
 *         @OA\Property(property="body", type="string")
 *     }
 * )
 */
class PostViewModel extends ViewModel
{
    protected $mappings = [
        'title' => 'title',
        'body' => 'body',
    ];
}

app/Transformers/PostTransformer.php

namespace App\Transformers;

/**
 * @OA\Schema(
 *     schema="PostResponse",
 *     type="object",
 *     title="PostResponse",
 *     properties={
 *         @OA\Property(property="id", type="integer"),
 *         @OA\Property(property="attributes", type="object", properties={
 *             @OA\Property(property="title", type="string"),
 *             @OA\Property(property="body", type="string")
 *         }),
 *         @OA\Property(property="relationships", type="array", @OA\Items({
 *
 *         })),
 *     }
 * )
 */
class PostTransformer extends Transformer
{
    public $type = 'post';

    protected $availableIncludes = ['user'];

    public function transform($user)
    {
        return [
            'id' => $user->id,
            'title' => $user->title,
            'body' => $user->body,
        ];
    }

    public function includeUser($post)
    {
        return $this->item($post->user, new UserTransformer(), 'user');
    }
}

app/Transformers/UserTransformer.php

namespace App\Transformers;

/**
 * @OA\Schema(
 *     schema="UserResponse",
 *     type="object",
 *     title="UserResponse",
 *     properties={
 *         @OA\Property(property="id", type="integer"),
 *         @OA\Property(property="attributes", type="object", properties={
 *             @OA\Property(property="name", type="string"),
 *             @OA\Property(property="email", type="string")
 *         }),
 *     }
 * )
 */
class UserTransformer extends Transformer
{
    public $type = 'user';

    /**
     * @param \App\Models\User $user
     * @return array
     */
    public function transform($user)
    {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
        ];
    }
}
Here are some examples for API routes.

app/Http/Controllers/PostController.php

namespace App\Http\Controllers;

use App\Models\Post;
use App\Transformers\PostTransformer;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * @OA\Get(
     *     path="/posts",
     *     summary="List all posts",
     *     operationId="index",
     *     tags={"Post"},
     *     @OA\Parameter(
     *         name="include",
     *         in="query",
     *         required=false,
     *         @OA\Schema(
     *             type="array",
     *             @OA\Items(
     *                 type="string",
     *                 enum = {"user"},
     *             )
     *         )
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="An paged array of posts",
     *         @OA\JsonContent(
     *             type="array",
     *             @OA\Items(ref="#/components/schemas/PostResponse")
     *         ),
     *     ),
     *     @OA\Response(
     *         response="default",
     *         description="unexpected error",
     *         @OA\Schema(ref="#/components/schemas/Error")
     *     )
     * )
     */
    public function index()
    {
        return $this->paginate(\App\Models\Post::paginate(5), new PostTransformer());
    }

    /**
     * @OA\Post(
     *     path="/posts",
     *     summary="New blog post",
     *     operationId="store",
     *     tags={"Post"},
     *     @OA\RequestBody(
     *         required=true,
     *         description="Post object",
     *         @OA\JsonContent(ref="#/components/schemas/PostRequest")
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="A post",
     *         @OA\JsonContent(ref="#/components/schemas/PostResponse"),
     *     ),
     *     @OA\Response(
     *         response="default",
     *         description="unexpected error",
     *         @OA\Schema(ref="#/components/schemas/Error")
     *     )
     * )
     * @param Request $request
     * @return array
     */
    public function store(Request $request)
    {
        $viewModel = new PostViewModel();
        $viewModel->mapArray($request->json()->all());

        // Transfer this on a service class or do some validation.
        $post = new Post();
        $post->title = $viewModel->title;
        $post->body = $viewModel->body;
        $post->save();

        return $this->item($post, new PostTransformer());
    }
}

Viewing on the browser

After editing the annotations you have to run the artisan command.

php artisan swg:scan

To view the documentation on the browser go to <your host>/swagger-ui.html.

Lumen API Tutorial - Documentation using Swagger UI

That is all. Thanks for reading!

References


  1. i need api documentation for routes


  2. I get this error “Call to undefined function OpenApi\scan()”

Post A Comment