Back to blog

P4 | API documentation using Swagger in Laravel

Mar 31, 2025
.
Kristína Odziomková

In one of the previous parts of this blog series, we scaffolded an API in Laravel with examples of login, logout and user detail endpoints, both in REST and GraphQL.

Unlike GraphQL, which is self-documenting through introspection and built-in schema descriptions, REST APIs require separate documentation, often maintained manually. Tools like Swagger (OpenAPI), Postman, or API Blueprint help document REST endpoints, request/response structures, and parameters, but they rely on developers to keep them updated. In contrast, GraphQL APIs always reflect the latest schema changes, making documentation more consistent and automatically available.

Since REST APIs require explicit documentation, let's explore how to create clear and structured API docs in Laravel using Swagger (thanks to packages such as L5 Swagger and Laravel Swagger UI)!


L5 Swagger

This package is useful when you want to annotate your own API methods and then generate Swagger documentation from these annotations.

Using OpenAPI specification, the annotations for our API would look like this:


<?php

namespace App\Http\Controllers;

use App\Http\Resources\EventCollection;
use App\Http\Resources\EventResource;
use App\Models\Event;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

/**
 * @OA\Info(
 *     title="Event API",
 *     version="1.0.0",
 *     description="API documentation for the event management system"
 * )
 *
 * @OA\SecurityScheme(
 *     securityScheme="bearerAuth",
 *     type="http",
 *     scheme="bearer"
 * )
 *
 * @OA\Schema(
 *     schema="EventCollection",
 *     type="object",
 *     title="Event Collection",
 *     @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/EventResource"))
 * )
 * @OA\Schema(
 *     schema="EventResource",
 *     type="object",
 *     title="Event Resource",
 *     @OA\Property(property="id", type="integer", example=1),
 *     @OA\Property(property="title", type="string", example="Concert"),
 *     @OA\Property(property="description", type="string", example="A live music event"),
 *     @OA\Property(property="date", type="string", format="date-time", example="2025-06-01T18:00:00Z")
 * )
 */
class EventController extends Controller
{
    /**
     * @OA\Get(
     *     path="/api/events",
     *     summary="Get all events",
     *     tags={"Events"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Response(
     *         response=200,
     *         description="Successful response",
     *         @OA\JsonContent(ref="#/components/schemas/EventCollection")
     *     )
     * )
     */
    public function get(): EventCollection
    {
        return new EventCollection(Event::all());
    }

    /**
     * @OA\Get(
     *     path="/api/events/{event}",
     *     summary="Get event details",
     *     tags={"Events"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="event",
     *         in="path",
     *         required=true,
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Successful response",
     *         @OA\JsonContent(ref="#/components/schemas/EventResource")
     *     )
     * )
     */
    public function detail(Request $request, Event $event): EventResource
    {
        return new EventResource($event);
    }

    /**
     * @OA\Post(
     *     path="/api/events/{event}/signup",
     *     summary="Sign up for an event",
     *     tags={"Events"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="event",
     *         in="path",
     *         required=true,
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(response=200, description="User signed up", @OA\JsonContent(ref="#/components/schemas/EventResource")),
     *     @OA\Response(response=400, description="Already participated or max participants reached")
     * )
     */
    public function signup(Request $request, Event $event): JsonResponse
    {
        $user = Auth::guard('mobile-app')->user();

        if($event->participants()->where('mobile_app_users.id', $user->id)->exists()) {
            return response()->json(['message' => 'Already participated'], 400);
        }

        if($event->participants()->count() >= $event->max_number_of_participants) {
            return response()->json(['message' => 'Max. number of participants reached'], 400);
        }

        $event->participants()->attach($user->id);

        return response()->json(new EventResource($event));
    }

    /**
     * @OA\Post(
     *     path="/api/events/{event}/signout",
     *     summary="Sign out from an event",
     *     tags={"Events"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="event",
     *         in="path",
     *         required=true,
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(response=200, description="User signed out", @OA\JsonContent(ref="#/components/schemas/EventResource"))
     * )
     */
    public function signout(Request $request, Event $event): JsonResponse
    {
        $user = Auth::guard('mobile-app')->user();

        $event->participants()->detach($user->id);

        return response()->json(new EventResource($event));
    }
}


<?php

namespace App\Http\Controllers;

use App\Http\Resources\EventCategoryCollection;
use App\Models\EventCategory;

/**
 * @OA\Schema(
 *     schema="EventCategoryCollection",
 *     type="object",
 *     title="Event Category Collection",
 *     @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/EventCategoryResource"))
 * )
 * @OA\Schema(
 *     schema="EventCategoryResource",
 *     type="object",
 *     title="Event Category Resource",
 *     @OA\Property(property="id", type="integer", example=1),
 *     @OA\Property(property="title", type="string", example="Music")
 * )
 */
class EventCategoryController extends Controller
{
    /**
     * @OA\Get(
     *     path="/api/event-categories",
     *     summary="Get event categories",
     *     tags={"Event Categories"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Response(
     *         response=200,
     *         description="Successful response",
     *         @OA\JsonContent(ref="#/components/schemas/EventCategoryCollection")
     *     )
     * )
     */
    public function get(): EventCategoryCollection
    {
        return new EventCategoryCollection(EventCategory::all());
    }
}


After generating the documentation using the Artisan command

php artisan l5-swagger:generate

the following documentation is created:

Swagger listing
Swagger listing
Swagger detail
Swagger detail

Laravel Swagger UI

In our use cases, this package has been useful when we had a Swagger .json file already prepared, e.g. with a proposal of an API that we needed from an external provider, and wanted to create an UI for it.

Let's say we need to pull in data about events from an external data provider and they asked us what endpoints and what return data we need.

Using the online Swagger editor, we would prepare an API definition like this:


{
    "openapi": "3.0.0",
    "info": {
        "title": "Event API",
        "description": "API documentation for the event management system",
        "version": "1.0.0"
    },
    "paths": {
        "/api/event-categories": {
            "get": {
                "tags": [
                    "Event Categories"
                ],
                "summary": "Get event categories",
                "operationId": "508bb6283928de129a1855a1935eb999",
                "responses": {
                    "200": {
                        "description": "Successful response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/EventCategoryCollection"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/events": {
            "get": {
                "tags": [
                    "Events"
                ],
                "summary": "Get all events",
                "operationId": "61e76f0fc328c54a3a110214de7cb208",
                "responses": {
                    "200": {
                        "description": "Successful response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/EventCollection"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        
        
    },
    "components": {
        "schemas": {
            "EventCategoryCollection": {
                "title": "Event Category Collection",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/EventCategoryResource"
                        }
                    }
                },
                "type": "object"
            },
            "EventCategoryResource": {
                "title": "Event Category Resource",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 1
                    },
                    "title": {
                        "type": "string",
                        "example": "Music"
                    }
                },
                "type": "object"
            },
            "EventCollection": {
                "title": "Event Collection",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/EventResource"
                        }
                    }
                },
                "type": "object"
            },
            "EventResource": {
                "title": "Event Resource",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 1
                    },
                    "title": {
                        "type": "string",
                        "example": "Concert"
                    },
                    "description": {
                        "type": "string",
                        "example": "A live music event"
                    },
                    "date": {
                        "type": "string",
                        "format": "date-time",
                        "example": "2025-06-01T18:00:00Z"
                    }
                },
                "type": "object"
            }
        },
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer"
            }
        }
    },
    "tags": [
        {
            "name": "Event Categories",
            "description": "Event Categories"
        },
        {
            "name": "Events",
            "description": "Events"
        }
    ]
}

After installing the Laravel Swagger UI package and following it's current documentation, specify in the swagger-ui.php config file the path to Swagger JSONs that you want shown in the Swagger UI. For example:


<?php
 'versions' => [
     'v1' => resource_path('swagger/v1.json'),
     'v2' => resource_path('swagger/v2.json'),
  ],

You should be able to see the Swagger UI automatically after navigating to the specified URL, in our case /swagger:L

Laravel Swagger UI
Laravel Swagger UI

Conclusion

To properly create your Swagger OpenAPI documentation, it's essential to understand the fundamentals of the OpenAPI specification. Fortunately, with the excellent packages we've discussed, the UI will be generated automatically for you!

We hope this blog post has been useful to you and that we will see you in the next part of this blog series soon!