[Symfony • Laravel] OpenAPI
Komparasi OpenAPI di Symfony dan Laravel
Wohh tunjek point 🥳
Gak juga. Maksud saya sedikit2 aja, sampai jadi ……………………………. gak tau jadi apa nanti 🙃
Jadi rencananya saya akan melakukan komparasi dalam konteks REST-API. Dengan kasus sederhana, namun memenuhi kriteria REST-API pada umumnya. Sehingga materi komparasi ini nanti bisa digunakan sebagai modal berkode serius. Bukan hanya sekadar membandingkan dua framework, apalagi sampe berantem, kayak buzzer aja 😮💨
Nah, di tulisan pertama ini saya akan mulai dari membuat OpenAPI alias Swagger.
Btw, API Platform sudah support Laravel juga lo, ngapain bikin manual begini? 🙄
Masalahnya tulisan ini dimulai sebelum API Platform rilis untuk Laravel 😭. Tapi gpp, API Platform merupakan pintu, yang di dalamnya kita tetap akan bersinggungan dengan Symfony dan Laravel juga. Jadi tidak perlu ada calo di antara kita dengan Symfony dan Laravel 😶
Calonya ya kamu 😤
Ampuunnnn 😭
OpenAPI
Kebetulan pernah buat tulisan tentang OpenAPI, baca sini dulu gaes 👉 OpenAPI.
Instalasi
Persiapkan PHP versi ≥ 8.2 dulu yak, lalu dilanjutkan dengan instalasi Composer.
- PHP
- Composer
Instalasi PHP di Windows boleh pakai paket hemat Laragon gak? 🤔
Boleh dongs, masak gak boleh 😘
Symfony
Selain PHP dan Composer, untuk Symfony saya sarankan juga melakukan instalasi Symfony-CLI untuk menunjang manajemen pengembangan aplikasi dengan Symfony. Setelah semua sudah siap, lakukan langkah2 instalasi berikut.
Cek kebutuhan pengembangan aplikasi dengan Symfony, sampai muncul keterangan “Your system is ready to run Symfony projects”. Jika belum berhasil, lengkapi dulu syarat2 tersebut.
symfony check:requirements
Membuat aplikasi dengan Symfony. symfony-api
bisa diganti apa aja ya, terserah kalian.
symfony new symfony-api --version="7.1.*"
Coba jalankan aplikasi, lalu buka browser dan masuk ke alamat https://symfony-api.wip.
cd symfony-api && symfony server:start
Jika ada masalah, kalian bisa ikuti petunjuk detailnya di sini. Atau bisa langsung dengan mengakses http://localhost:8000 tanpa local domain.
Laravel
Laravel sebenarnya juga memiliki installer, tapi saya belum bisa merekomendasikannya karena perannya hanya sebatas sebagai installer. Jadi cukup kita manfaatkan Composer saja ya gaes.
Langsung aja lakukan instalasi. laravel-api
juga bisa kalian ganti bebas.
composer create-project laravel/laravel laravel-api
Coba jalankan aplikasi, lalu buka browser dan masuk ke alamat http://localhost:8001. Diberi tambahan --port=8001
agar tidak bentrok dengan server-nya Symfony.
cd laravel-api && php artisan serve --port=8001
Instalasi OpenAPI
Baik Symfony maupun Laravel, sama2 punya paket khusus untuk menyediakan fitur OpenAPI. Sehingga implementasinya pun cukup mudah.
Hmmm cukup mudah, mencurigakan... 🙄
Symfony
Di Symfony terdapat bundle (sebutan paket di Symfony) untuk menyediakan fitur OpenAPI, yaitu NelmioApiDocBundle.
Instalasi menggunakan Composer.
composer require nelmio/api-doc-bundle twig asset
Dilanjut dengan membuka comment di config/routes/nelmio_api_doc.yaml
bagian app.swagger_ui
sehingga menjadi seperti berikut.
app.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
Lakukan penyesuaian seperlunya di config/packages/nelmio_api_doc.yaml
misalnya menjadi seperti ini.
nelmio_api_doc:
documentation:
info:
title: Symfony API
description: Mainan API dengan Symfony
version: 1.0.0
accept_type: 'application/json'
body_format:
formats: ['json']
default_format: 'json'
request_format:
formats:
json: 'application/json'
areas: # to filter documented areas
path_patterns:
- ^/api(?!/doc$) # Accepts routes under /api except /api/doc
Selesai, buka OpenAPI melalui https://symfony-api.wip/api/doc.
Laravel
Di Laravel, yang saya tahu, paket yang banyak digunakan dan masih aktif maintenis sampai saat ini adalah L5-Swagger.
Langsung sikat aja gaes.
composer require darkaonline/l5-swagger
Tambahkan nilai berikut ke .env
.
L5_SWAGGER_GENERATE_ALWAYS=true
Publikasikan konfigurasi L5-Swagger.
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
Buka OpenAPI melalui http://127.0.0.1:8001/api/documentation.
Tenang, error tersebut wajar. Karena butuh sentuhan lagi untuk menghasilkan tampilan yang kurang lebih seperti yang dihasilkan NelmioApiDocBundle. Tapi untuk persiapan OpenAPI, keduanya sudah cukup, lanjut.
Bentar2, istirahat dulu 🥵
Gak sambil lari kan gaes? 😶
Database dan entity (model)
Nah, urusan database ini terserah ya mau pakai database apa. SQLite boleh, PostgreSQL boleh, MySQL boleh, SQL Server boleh, silakan tentukan sendiri. Yang saya contohkan di bawah ini menggunakan PostgreSQL.
Yaudah, saya pakai SQLite 😋
Nahhh 🔥
Symfony
Instalasi kebutuhan database management di Symfony.
composer require symfony/orm-pack
composer require --dev symfony/maker-bundle
Di file .env
, tentukan koneksinya sesuai database yang kalian pilih.
DATABASE_URL="postgresql://<user>:<password>@127.0.0.1:5432/<dbName>?serverVersion=<serverVersion>&charset=utf8"
Buat database. Nama database yang terbuat akan sesuai dengan <dbName>
yang ditentukan sebelumnya.
symfony console doctrine:database:create
Buat entity. Entity adalah representasi tabel di database yang akan digunakan di aplikasi Symfony. Buat dengan nama Product
, dengan kolom name
tipe string
, price
tipe decimal
, createdAt
tipe datetime_immutable
, dan updatedAt
tipe datetime_immutable
.
symfony console make:entity
Hasil generasinya kurang lebih seperti berikut, menjadi file src/Entity/Product.php
.
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 50)]
private ?string $name = null;
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2)]
private ?string $price = null;
#[ORM\Column]
private ?\DateTimeImmutable $createdAt = null;
#[ORM\Column]
private ?\DateTimeImmutable $updatedAt = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getPrice(): ?string
{
return $this->price;
}
public function setPrice(string $price): static
{
$this->price = $price;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeImmutable $createdAt): static
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeImmutable $updatedAt): static
{
$this->updatedAt = $updatedAt;
return $this;
}
}
Buat file migration.
symfony console make:migration
Lakukan migrasi, database siap digunakan.
symfony console doctrine:migrations:migrate
Laravel
Di Laravel, urusan database sudah tersedia tanpa instalasi paket tambahan, tinggal sikat aja.
Pertama ubah file .env
, tentukan koneksinya sesuai database yang kalian pilih. Lalu buat database manual sesuai <dbName>
yang ditentukan.
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=<dbName>
DB_USERNAME=<user>
DB_PASSWORD=<password>
Reload config. Lakukan setiap kali mengubah nilai dari .env
.
php artisan config:cache
Buat model Product
, yang juga merepresentasikan tabel di database, melalui migration. Yups, bedanya dengan entity di Symfony, model di Laravel belum merepresentasikan tabel yang sebenarnya.
php artisan make:model Product --migration
Hasil generasi modelnya kurang lebih seperti berikut. Lihat, yang mewakili tabel hanya nama modelnya saja.
class Product extends Model
{
use HasFactory;
}
Ubah file migration yang telah terbuat di bagian create products, menjadi seperti yang ada di Symfony. Nah, file migration ini yang sebenarnya merupakan representasi tabel di database.
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->timestamps();
// tambahkan name dan price
$table->string('name', 50);
$table->decimal('price', 10, 2);
});
Lakukan migrasi, database siap digunakan.
php artisan migrate
Woke gaes, urusan database selesai 😋
Huwaaaaa 🥵
Kan, jangan sambil lari gaes 🙄
Routing dan controller
Selanjutnya kita akan buat endpoint yang tersedia untuk OpenAPI.
Symfony
Buat controller ProductController
.
symfony console make:controller ProductController --no-template
Kurang lebih akan menghasilkan kode seperti ini.
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
class ProductController extends AbstractController
{
#[Route('/product', name: 'app_product')]
public function index(): JsonResponse
{
return $this->json([
'message' => 'Welcome to your new controller!',
'path' => 'src/Controller/ProductController.php',
]);
}
}
Nah, mari kita sesuaikan beberapa hal terutama di method index()
agar dapat dikenali sebagai endpoint GET /api/products
di OpenAPI.
<?php
namespace App\Controller;
use App\Entity\Product;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
final class ProductController extends AbstractController
{
/**
* Retrieves the collection of product resources
*/
#[Route('/api/products', methods: ['GET'])]
#[OA\Tag(name: 'Products')]
#[OA\Parameter(
name: 'name',
in: 'query',
description: 'Search by product name',
schema: new OA\Schema(type: 'string'),
)]
#[OA\Response(
response: 200,
description: 'Product collection',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(ref: new Model(type: Product::class))
)
)]
public function findMany(): JsonResponse
{
return $this->json([]);
}
}
- Method
index()
diubah menjadifindMany()
, dengan keluaran array kosong[]
untuk sementara. - Attribute
Route
diubah menjadi#[Route('/api/products', methods: ['GET'])]
. - Tambah attribute
OA\Tag
sebagai segmen di OpenAPI, dengan namaProduct
. - Tambah attribute
OA\Parameter
sebagai parameter yang diakomodir oleh endpoint dalam bentuk query string, kita sediakanname
untuk kebutuhan pencarian data berdasarkan nama produk. - Tambah attribute
OA\Response
sebagai gambaran response yang dihasilkan dari endpoint, dalam bentuk array dengan schema dari entityProduct
.
Refresh halaman https://symfony-api.wip/api/doc, buka endpoint GET /api/products
, tampilannya kira2 seperti ini. Try it out!
Laravel
Dimulai dari instalasi API routes dulu. Yang kita butuhkan dari perintah ini sebenarnya hanya untuk mendaftarkan manajemen route khusus API, meskipun turut terinstal juga Laravel Sanctum yang sementara ini belum kita butuhkan.
php artisan install:api
Buat controller ProductController
.
php artisan make:controller ProductController --api
Controller yang dihasilkan kurang lebih seperti ini.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProductController extends Controller
{
// sudah ada banyak method yang tersedia
}
Mari kita sesuaikan seperti yang ada di Symfony sebelumnya.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use OpenApi\Attributes as OA;
class ProductController extends Controller
{
/**
* Retrieves the collection of product resources
*/
#[OA\Get(path: '/api/products', tags: ['Product'])]
#[OA\Parameter(
name: 'name',
in: 'query',
description: 'Search by product name',
schema: new OA\Schema(type: 'string'),
)]
#[OA\Response(
response: 200,
description: 'Product collection',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(ref: '#/components/schemas/Product')
),
)]
public function findMany(): JsonResponse
{
return response()->json([]);
}
}
- Hapus semua method, buat method baru bernama
findMany()
. - Semua struktur OpenAPI sama persis seperti yang ada di Symfony, hanya saja ada pendekatan yang berbeda. Di Laravel, deklarasi endpoint menggunakan
OA\Get
disertaitags
. - Schema Product-nya dideklarasikan di model
Product
dan dicantumkan dengan format#/components/schemas/<schemaName>
.
Nyok buat schema-nya di model Product
.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA;
#[OA\Schema(
properties: [
new OA\Property(
property: 'name',
type: 'string',
),
new OA\Property(
property: 'price',
type: 'string',
),
]
)]
class Product extends Model
{
use HasFactory;
}
Ingat halaman OpenAPI dari Laravel yang masih error? Yups, mari kita beri sentuhan di app/Http/Controllers/Controller.php
.
<?php
namespace App\Http\Controllers;
use OpenApi\Attributes as OA;
#[OA\OpenApi(
info: new OA\Info(
version: '1.0.0',
title: 'Laravel API',
description: 'Mainan API dengan Laravel'
)
)]
abstract class Controller
{
//
}
Woke, urusan OpenAPI sudah selesai, tapi urusan routing belum. Ubah routes/api.php
sesuai dengan kebutuhan seperti di Symfony. Route yang dibuat di bawah ini akan menghasilkan endpoint /api/products
.
<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::prefix('products')->group(function () {
Route::get('/', [ProductController::class, 'findMany']);
});
Selesai, refresh halaman http://localhost:8001/api/documentation, buka endpoint GET /api/products
, tampilannya kira2 seperti ini. Try it out!
Selesai
Sudah2, cukup… 🥵
Iya iya… 😓 Btw, kamu dapat apa dari praktik ini gaes?
Oke begini. Ternyata OpenAPI itu sekadar eksposur endpoint aja ya, bukan endpoint itu sendiri. Misalnya di Laravel, terlihat jelas bedanya antara kita menyediakan endpoint dengan menyediakan endpoint untuk OpenAPI. Deklarasi endpoint ada di file routes/api.php
, sedangkan deklarasi endpoint untuk OpenAPI berada di file berbeda.
Keren sekali. Yups betul gaes, itu sebabnya OpenAPI ini bisa juga disebut dokumentasi API. Tujuannya agar pihak yang memanfaatkan API memiliki referensi teknis tentang apa dan bagaimana penggunaannya. Misalnya dalam suatu organisasi, tim teknis dibagi menjadi backend dan frontend. OpenAPI adalah jembatan komunikasi teknis antara frontend dan backend.
Ada lagi gaes?
Lanjut gaes, belum apa2 ini 😋
Wooooo katanya sudah cukup 😤