API documentation becomes very necessary when you split the team into Backend and Frontend. And even more when you divide your monorepo into parts or even microservices.
I will show you how easily create API documentation for your Laravel API using swagger.
Let’s start. I prefer using this package. This package is a wrapper of Swagger-php and swagger-ui adapted to work with Laravel.
composer require "darkaonline/l5-swagger"
Then publish config and all view files:
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
Next, open a config/l5-swagger.php file. Let’s walk through essential keys:
- routes.api — This is an URL for accessing documentation UI. Your frontend team will be using it to access documentation. By default, it is api/documentation . I prefer changing it to something smaller like api/docs
- generate_always — I prefer disabling it as it will generate docs on the fly. Not useful with big API. You can always manually run php artisan l5-swagger:generate
Those are the most important to start. Now if you try to create docs using this command
php artisan l5-swagger:generate
It will return an error
Required @OA\Info() not found
That means that you have to create that notation first. So let’s add it. I prefer creating Abstract controller for an API, but you can add this to app/Http/Controllers/Controller.php
/**
* @OA\Info(
* title="Your super ApplicationAPI",
* version="1.0.0",
* )
*/
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
Next, we need to add docs for at least one route. Let’s add it for app/Http/Controllers/Auth/LoginController.php:
/**
* @OA\Post(
* path="/login",
* summary="Sign in",
* description="Login by email, password",
* operationId="authLogin",
* tags={"auth"},
* @OA\RequestBody(
* required=true,
* description="Pass user credentials",
* @OA\JsonContent(
* required={"email","password"},
* @OA\Property(property="email", type="string", format="email", example="user1@mail.com"),
* @OA\Property(property="password", type="string", format="password", example="PassWord12345"),
* @OA\Property(property="persistent", type="boolean", example="true"),
* ),
* ),
* @OA\Response(
* response=422,
* description="Wrong credentials response",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="Sorry, wrong email address or password. Please try again")
* )
* )
* )
*/
Now, you are ready to go. Run php artisan l5-swagger:generate and go to the URL that you provided in your config. In my case, it will be http://127.0.0.1:8000/api/docs.
You will see something like that:
Swagger UI main page
Now let’s dig into annotations. I will try to explain how to use them:
- @OA — means Open API annotation. You can read more here
- @OA\Post — means POST request. There are GET, POST, DELETE, etc.
- Path — it’s an URL
- Tags — it will group your API by sections.
- @OA\RequestBody — it’s obvious from the name. It should have JsonContent annotation inside with Property annotations(i.e., field descriptions).
- @OA\Response — you can have as many responses as you want. You should provide all possible success and error responses here.
Let’s add a 200 response code:
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* @OA\Property(property="user", type="object", ref="#/components/schemas/User"),
* )
* ),
@OA\Property annotation has a property key(field name) and type. Type can have different values: string, object, integer, array, boolean, etc.
In this response, I used the type object. You can pass a reference to that object. Let’s create a User object. I prefer adding that in Model class.
/**
*
* @OA\Schema(
* required={"password"},
* @OA\Xml(name="User"),
* @OA\Property(property="id", type="integer", readOnly="true", example="1"),
* @OA\Property(property="role", type="string", readOnly="true", description="User role"),
* @OA\Property(property="email", type="string", readOnly="true", format="email", description="User unique email address", example="user@gmail.com"),
* @OA\Property(property="email_verified_at", type="string", readOnly="true", format="date-time", description="Datetime marker of verification status", example="2019-02-25 12:59:20"),
* @OA\Property(property="first_name", type="string", maxLength=32, example="John"),
* @OA\Property(property="last_name", type="string", maxLength=32, example="Doe"),
* @OA\Property(property="created_at", ref="#/components/schemas/BaseModel/properties/created_at"),
* @OA\Property(property="updated_at", ref="#/components/schemas/BaseModel/properties/updated_at"),
* @OA\Property(property="deleted_at", ref="#/components/schemas/BaseModel/properties/deleted_at")
* )
*
* Class User
*
*/
Take a look at this notation @OA\Xml(name=” User”). This name will be used in a ref key of the @OA\Property
As you can see I used #/components/schemas/BaseModel/properties/* for created_at, updated_at and deleted_at. I prefer creating abstract BaseModel with repeatable annotations, but you can put them anywhere you want. It is just my preferable way of structuring it.
/**
* @OA\Schema(
* @OA\Property(property="created_at", type="string", format="date-time", description="Initial creation timestamp", readOnly="true"),
* @OA\Property(property="updated_at", type="string", format="date-time", description="Last update timestamp", readOnly="true"),
* @OA\Property(property="deleted_at", type="string", format="date-time", description="Soft delete timestamp", readOnly="true"),
* )
* Class BaseModel
*
* @package App\Models
*/
abstract class BaseModel extends Model {}
Another tip for authorizing some URLs with JWT. You can add a security parameter this way:
/**
* @OA\Post(
* path="/v1/logout",
* summary="Logout",
* description="Logout user and invalidate token",
* operationId="authLogout",
* tags={"auth"},
* security={ {"bearer": {} }},
* @OA\Response(
* response=200,
* description="Success"
* ),
* @OA\Response(
* response=401,
* description="Returns when user is not authenticated",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="Not authorized"),
* )
* )
* )
*/
Now you will see a locker icon near the route. When you click on that, you will be able to add Bearer token
A few more examples of different @OA\Property types:
- You can use predefined format=” email” and even regexp pattern.
@OA\Property(property="email", type="string", pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$", format="email", example="user2@gmail.com"),
2. You can use type=” array” and collectionFormat=” multi” to describe an array of validation errors. But you have to define @OA\Items annotation
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="The given data was invalid."),
* @OA\Property(
* property="errors",
* type="object",
* @OA\Property(
* property="email",
* type="array",
* collectionFormat="multi",
* @OA\Items(
* type="string",
* example={"The email field is required.","The email must be a valid email address."},
* )
* )
* )
* )
* )
3. You can provide a binary image as an example if your backend supports that
@OA\Property(property="picture", type="string", format="base64", example="data:image/jpeg;base64, yourSuperLongStringBinary"),
or like binary
@OA\Property(property="file", type="string", format="binary"),
4. When you need to describe an array of objects you can use type=” array” and pass object via @OA\Items
@OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/City"))
And a few more advanced examples with parameters and combined/complex schemas
- You can create combined schemas using allOf,anyOf, etc. profileGet schema will contain User object, an array of OrderCategory objects, etc.
/**
* @OA\Schema(
* schema="profileGet",
* allOf={
* @OA\Schema(ref="#/components/schemas/User"),
* @OA\Schema(
* @OA\Property(property="categories", type="array", @OA\Items(ref="#/components/schemas/OrderCategory")),
* ),
* @OA\Schema(
* @OA\Property(property="locations", type="array", @OA\Items(ref="#/components/schemas/stateCounties")),
* ),
* @OA\Schema(
* @OA\Property(property="avatar", type="object", ref="#/components/schemas/File"),
* ),
* @OA\Schema(
* @OA\Property(property="address", type="object", ref="#/components/schemas/AddressCoordinates"),
* )
* }
* )
*
* @OA\Get(
* path="/v1/profile",
* summary="Retrieve profile information",
* description="Get profile short information",
* operationId="profileShow",
* tags={"profile"},
* security={ {"bearer": {} }},
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* @OA\Property(property="data", type="object", ref="#/components/schemas/profileGet")
* )
* ),
* @OA\Response(
* response=401,
* description="User should be authorized to get profile information",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="Not authorized"),
* )
* )
* )
*/
2. Whenever you need to describe parameter in URL (e.g. /v1/geo/cities/{cityId}/zip_codes )you can use @OA\Parameter.
/**
* @OA\Get(
* path="/v1/geo/cities/{cityId}/zip_codes",
* summary="List of zip codes by city",
* description="Get list of zip codes by city",
* operationId="geoZipCodes",
* tags={"geo"},
* security={ {"bearer": {} }},
* @OA\Parameter(
* description="ID of city",
* in="path",
* name="cityId",
* required=true,
* example="1",
* @OA\Schema(
* type="integer",
* format="int64"
* )
* )
* )
*/
There are many other cool features and options that you can use to create informative API documentation. I tried to cover the most important things that took a lot of my time when I was creating swagger documentation. Feel free to share your snippets!
Original post: https://ivankolodiy.medium.com/how-to-write-swagger-documentation-for-laravel-api-tips-examples-5510fb392a94
Materials:
- https://github.com/DarkaOnLine/L5-Swagger
- https://swagger.io/docs/specification/about/
- https://github.com/zircote/swagger-php
- https://github.com/swagger-api/swagger-ui
My socials:
https://ivankolodiy.medium.com/
https://www.facebook.com/kolodii.ivan