💡 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