API documentation becomes necessary when you split the team into Backend and Frontend, especially with monorepos or microservices architecture.
Getting Started
Install the L5-Swagger package, which wraps Swagger-php and swagger-ui for Laravel:
composer require "darkaonline/l5-swagger"
Publish configuration and views:
php artisan vendor:publish --provider "L5Swagger\\L5SwaggerServiceProvider"
Configuration
Key settings in config/l5-swagger.php:
- routes.api — URL for documentation UI (default:
api/documentation, recommendapi/docs) - generate_always — Set to false to prevent on-the-fly generation for large APIs
Creating Base Documentation
Add @OA\Info() notation to your base controller to generate docs:
/**
* @OA\Info(
* title="Your super Application API",
* version="1.0.0",
* )
*/
class Controller extends BaseController
{
// ...
}
Generate documentation:
php artisan l5-swagger:generate
Access at http://127.0.0.1:8000/api/docs
Documenting Routes
Example POST endpoint with request body and response:
/**
* @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")
* )
* )
* )
*/
Key Annotations Explained
- @OA — OpenAPI annotation (see OpenAPI specification)
- @OA\Post|Get|Delete — HTTP methods
- path — URL endpoint
- tags — Groups related APIs
- @OA\RequestBody — Request parameters with JsonContent and Property annotations
- @OA\Response — All possible responses (success and error codes)
Adding Success Responses
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* @OA\Property(property="user", type="object", ref="#/components/schemas/User"),
* )
* ),
Creating Reusable Schemas
Define a User schema in your Model:
/**
* @OA\Schema(
* required={"password"},
* @OA\Xml(name="User"),
* @OA\Property(property="id", type="integer", readOnly="true", example="1"),
* @OA\Property(property="email", type="string", format="email", example="user@gmail.com"),
* @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
*/
Creating Abstract BaseModel for Reusable Properties
/**
* @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
*/
abstract class BaseModel extends Model {}
JWT Bearer Token Authentication
Add security to protected routes:
/**
* @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"),
* )
* )
* )
*/
This displays a lock icon in Swagger UI for token entry.
Advanced Property Types
Email with Pattern Validation
@OA\Property(property="email", type="string", pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$", format="email", example="user2@gmail.com"),
Array of Validation Errors
* @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."},
* )
* )
* )
* )
* )
Base64 Image
@OA\Property(property="picture", type="string", format="base64", example="data:image/jpeg;base64, yourSuperLongStringBinary"),
Array of Objects
@OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/City"))
Combined Schemas (allOf, anyOf)
/**
* @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"),
* )
* )
* )
*/
URL Path Parameters
/**
* @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"
* )
* )
* )
*/