Today, we will learn about creating beautiful API documentation with Swagger in Laravel. API documentation is very important when working in a team. It helps to determine the endpoints and parameters required for any request.
In this article, we will use a package known as DarkaOnLine/L5-Swagger in our Laravel application. This package is popular and specifically built to use on Laravel applications.
Before we head towards writing API documentation, we must install this package by running the below command.
composer require "darkaonline/l5-swagger"
This will install the package. Then we can publish the configuration and view files with the below command.
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
Now, let’s set up our project to create APIs with CRUD functionality for Article. Run the below command to create migration for articles
table.
php artisan make:migration create_articles_table
We will add some columns on the table. So that our final migrations for articles table looks like below.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content')->nullable();
$table->enum('status', ['Published', 'Draft'])->default('Draft');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('articles');
}
};
Run the migration to create table.
php artisan migrate
Let’s create factory to seed the articles table in the database.
php artisan make:factory Article
Open up the newly created ArticleFactory.php
and populate the columns using faker. Faker is a library that generates fake data for us. If you want to know more about faker, you can read this article.
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Article>
*/
class ArticleFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'title' => $this->faker->realText($maxNbChars = 50, $indexSize = 2),
'content' => $this->faker->paragraph(10, false),
'status' => $this->faker->randomElement(['Published', 'Draft'])
];
}
}
Add this seeder file in the /seeders/DatabaseSeeder.php
file.
<?php
//database/seeders/DatabaseSeeder.php
................
public function run()
{
//seed users table
\App\Models\User::factory(2)->create();
//seed articles table
\App\Models\Article::factory(50)->create();
}
Run the seed. This command will populate our articles table with specified(50) new records.
php artisan db:seed
Now, let’s create a model for the articles
table. Run the command.
php artisan make:model Article
We will change few settings in our model. The Article.php
file looks as below.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use HasFactory;
protected $fillable = ['title', 'content', 'status'];
}
Now, after all our setups are done, we will move into creating the API documentation. First of all, we need to give a description of the API. To do so, we need to modify our Controller
class. We will add a few descriptions above the class inside the comment section. Please note that the code inside comment section with proper format will be used as API documentation.
<?php
//app/Http/Controllers/Controller.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(
* version="1.0.0",
* title="Kodementor Api Documentation",
* description="Kodementor Api Documentation",
* @OA\Contact(
* name="Vijay Rana",
* email="info@kodementor.com"
* ),
* @OA\License(
* name="Apache 2.0",
* url="http://www.apache.org/licenses/LICENSE-2.0.html"
* )
* ),
* @OA\Server(
* url="/api/v1",
* ),
*/
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
This document inside will generate the following description of the API.

Please note that we will use /api/v1
route for the API. so, we will need to add v1
to the API route in RouteServiceProvider
.
<?php
//app/Providers/RouteServiceProvider.php
..........
public function boot()
{
$this->configureRateLimiting();
//please note the /v1 route prefix
$this->routes(function () {
Route::prefix('api/v1')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
});
}
Now, here comes the final part of writing documentation for the API. We store all the API controller inside a separate api/
directory. We will create API documentation for the index
, store
, edit
, update
and delete
methods. I have added the API documentation above each method. Thus, our final ArticleController.php
file looks like below.
<?php
namespace App\Http\Controllers\Api;
use Exception;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Api\BaseController;
class ArticleController extends Controller
{
/**
* @OA\Get(
* path="/articles",
* operationId="index",
* tags={"Articles"},
* summary="Get list of articles",
* description="Get list of articles",
* @OA\Parameter(name="limit", in="query", description="limit", required=false,
* @OA\Schema(type="integer")
* ),
* @OA\Parameter(name="page", in="query", description="the page number", required=false,
* @OA\Schema(type="integer")
* ),
* @OA\Parameter(name="order", in="query", description="order accepts 'asc' or 'desc'", required=false,
* @OA\Schema(type="string")
* ),
* @OA\Response(
* response=200, description="Success",
* @OA\JsonContent(
* @OA\Property(property="status", type="integer", example="200"),
* @OA\Property(property="data",type="object")
* )
* )
* )
*/
public function index(Request $request)
{
try {
$limit = $request->limit ?: 15;
$order = $request->order == 'asc' ? 'asc' : 'desc';
$articles = Article::orderBy('updated_at', $order)
->select('id', 'title', 'content', 'status')
->where('status', 'Published')
->paginate($limit);
return response()->json(['status' => 200, 'data' => $articles]);
} catch (Exception $e) {
return response()->json(['status' => 400, 'message' => $e->getMessage()]);
}
}
/**
* @OA\Post(
* path="/articles",
* operationId="store",
* tags={"Articles"},
* summary="Store article in DB",
* description="Store article in DB",
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"title", "content", "status"},
* @OA\Property(property="title", type="string", format="string", example="Test Article Title"),
* @OA\Property(property="content", type="string", format="string", example="This is a description for kodementor"),
* @OA\Property(property="status", type="string", format="string", example="Published"),
* ),
* ),
* @OA\Response(
* response=200, description="Success",
* @OA\JsonContent(
* @OA\Property(property="status", type="integer", example=""),
* @OA\Property(property="data",type="object")
* )
* )
* )
*/
public function store(Request $request)
{
try {
DB::beginTransaction();
$article = Article::create($request->only('title', 'content', 'status'));
DB::commit();
return response()->json(['status' => 201, 'data' => $article]);
} catch (Exception $e) {
DB::rollBack();
return response()->json(['status' => 400, 'message' => $e->getMessage()]);
}
}
/**
* @OA\Get(
* path="/articles/{id}",
* operationId="show",
* tags={"Articles"},
* summary="Get Article Detail",
* description="Get Article Detail",
* @OA\Parameter(name="id", in="path", description="Id of Article", required=true,
* @OA\Schema(type="integer")
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* @OA\Property(property="status_code", type="integer", example="200"),
* @OA\Property(property="data",type="object")
* ),
* )
* )
* )
*/
public function show(Article $article)
{
try {
return response()->json(['status' => 200, 'data' => $article]);
} catch (Exception $e) {
return response()->json(['status' => 400, 'message' => $e->getMessage()]);
}
}
/**
* @OA\Put(
* path="/articles/{id}",
* operationId="update",
* tags={"Articles"},
* summary="Update article in DB",
* description="Update article in DB",
* @OA\Parameter(name="id", in="path", description="Id of Article", required=true,
* @OA\Schema(type="integer")
* ),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"title", "content", "status"},
* @OA\Property(property="title", type="string", format="string", example="Test Article Title"),
* @OA\Property(property="content", type="string", format="string", example="This is a description for kodementor"),
* @OA\Property(property="status", type="string", format="string", example="Published"),
* ),
* ),
* @OA\Response(
* response=200, description="Success",
* @OA\JsonContent(
* @OA\Property(property="status_code", type="integer", example="200"),
* @OA\Property(property="data",type="object")
* )
* )
* )
*/
public function update(Request $request, $id)
{
try {
DB::beginTransaction();
$article = Article::updateOrCreate(['id' => $id], $request->only('title', 'content', 'status'));
DB::commit();
return response()->json(['status' => 200, 'data' => $article]);
} catch (Exception $e) {
DB::rollback();
return response()->json(['status' => 400, 'message' => $e->getMessage()]);
}
}
/**
* @OA\Delete(
* path="/articles/{id}",
* operationId="destroy",
* tags={"Articles"},
* summary="Delete Article",
* description="Delete Article",
* @OA\Parameter(name="id", in="path", description="Id of Article", required=true,
* @OA\Schema(type="integer")
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* @OA\Property(property="status_code", type="integer", example="200"),
* @OA\Property(property="data",type="object")
* ),
* )
* )
* )
*/
public function destroy($id)
{
try {
Article::find($id)->delete();
return response()->json(['status' => 200, 'data' => []]);
} catch (Exception $e) {
return response()->json(['status' => 400, 'message' => $e->getMessage()]);
}
}
}
In the above code, we will accept multiple parameters for the index method. This is because the ordering and pagination will be handled by the end user. Thus, we will fetch the record and respond accordingly.

While executing the API, we can get the below response. You can test it with other APIs as well.

This is all about swagger implementation in Laravel for API documentation. Furthermore, we can also format the response sent to the API with API Resource. We will come up with the documentation as well.
Please drop your thoughts in the comment section below.