Belajar Symfony - API Platform & REST API
Episode 13 of 27

Belajar Symfony - API Platform & REST API

Membangun REST API production-grade dengan API Platform: menandai entity dengan ApiResource, mengatur operasi & filters, pagination & sorting, dokumentasi OpenAPI/Swagger otomatis, content negotiation JSON-LD/HAL, serta validasi input dari sisi API.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Di episode 12 aplikasi sudah aman. Episode 13 menjawab kebutuhan berikutnya yang tak terelakkan di 2026: API. SPA, mobile app, dan integrasi pihak ketiga semuanya butuh endpoint yang rapi, terdocument, dan konsisten. API Platform — yang dibangun di atas Symfony — mengubah pembuatan API dari pekerjaan manual menjadi deklaratif.

Mengapa API Platform, bukan menulis controller JSON manual? Karena API butuh banyak hal yang mudah lupa: dokumentasi OpenAPI, pagination, filter, validasi, format konten (JSON-LD/HAL), dan security. API Platform menyediakan semuanya dari entity yang sama dengan yang sudah kalian punya sejak episode 6.

Instalasi dan Resource Pertama

Install API Platform
composer require api

Installasi ini mengaktifkan route /api, dokumentasi Swagger di /api/docs, dan integrasi dengan entity. Sekarang tandai entity sebagai resource API:

Entity sebagai API resource
use ApiPlatform\Metadata\ApiResource;
 
#[ApiResource]
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
    // kolom yang sudah ada sejak episode 6...
}

Satu attribute #[ApiResource] sudah menghasilkan endpoint CRUD lengkap:

MethodRouteFungsi
GET/api/articlesKoleksi artikel
POST/api/articlesBuat artikel
GET/api/articles/{id}Detail artikel
PUT / PATCH/api/articles/{id}Update artikel
DELETE/api/articles/{id}Hapus artikel

Coba langsung di browser: buka /api/articles — kalian akan melihat JSON-LD dengan properti seperti @id, @type, dan hydra:member. Buka /api/docs untuk dokumentasi interaktif.

Mengontrol Operasi

Tidak semua entity perlu seluruh operasi. Batasi lewat attribute:

Batasi operasi
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
 
#[ApiResource(
    operations: [
        new Get(),
        new GetCollection(),
    ],
)]
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
}

Dengan ini, /api/articles hanya read-only — tidak ada POST/PUT/DELETE. Ini contoh bagus bagaimana API Platform membuat kebijakan API terlihat eksplisit di deklarasi resource, bukan tersebar di kode.

Filter, Pagination, dan Sorting

Koleksi API tanpa filter tidak berguna di dataset nyata. Aktifkan filter bawaan:

Filter dan sorting
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Metadata\ApiFilter;
 
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: ['title' => 'ipartial'])]
#[ApiFilter(DateFilter::class, properties: ['createdAt'])]
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
}

Query yang tersedia:

Contoh query
GET /api/articles?title=Symfony&order[createdAt]=desc&page=2
GET /api/articles?createdAt[before]=2026-08-01

Pagination & sorting diatur global di config/packages/api_platform.yaml:

Konfigurasi pagination
api_platform:
    defaults:
        pagination:
            enabled: true
            items_per_page: 15
            maximum_items_per_page: 50
            client_items_per_page: true

client_items_per_page: true memperbolehkan klien meminta ?itemsPerPage=... dalam batas maksimum.

Content Negotiation: JSON-LD, HAL, dan Lainnya

API Platform mengirim JSON-LD secara default. Request dengan header Accept yang berbeda mengubah format:

Negosiasi konten
Accept: application/ld+json      → JSON-LD (default)
Accept: application/hal+json     → HAL
Accept: application/json         → JSON polos
Accept: application/merge-patch+json → untuk PATCH

Negosiasi ini penting: format adalah kontrak — SPA kalian bisa memilih mana yang paling nyaman tanpa mengubah kode server. Untuk JSON murni (tanpa hypermedia), set formats di resource:

Batasi format
api_platform:
    defaults:
        formats:
            jsonld: ['application/ld+json']
            json: ['application/json']
            html: ['text/html']

Validasi di API

Constraint validation episode 7 langsung berlaku di API — tidak perlu menulis ulang:

Constraint dipakai API
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
private ?string $title = null;

Kirim POST tanpa title → respons 422 Unprocessable Content dengan daftar violations yang rapi. Satu definisi validasi dipakai untuk form dan API — ini salah satu keunggulan terbesar.

Security di API

Gabungkan dengan episode 12: proteksi resource per role.

Security di resource
#[ApiResource(
    security: 'is_granted("ROLE_USER")',
    operations: [
        new GetCollection(),
        new Get(),
        new Post(security: 'is_granted("ROLE_USER")'),
        new Delete(security: 'is_granted("ROLE_ADMIN")'),
    ],
)]

Ungkapan security dievaluasi per operasi; securityPostDenormalize menangani kasus berbasis objek (misal hanya penulis yang bisa PUT). Response otomatis 403 saat tidak berwenang.

Tip

Mulai dari operasi yang paling sedikit dulu — read-only (GET) — lalu perluas. API yang terlalu terbuka lebih sulit ditutup daripada API yang tertutup lalu dibuka bertahap. Security lebih mudah ditambah ketika dideklarasikan sejak awal resource.

Common Pitfalls

  • Entity terekspos semua kolom — batasi dengan normalizationContext/denormalizationContext (misal groups: ['article:read']) agar properti internal (seperti hash password) tidak bocor.
  • Lupa filter aktif — filter tidak aktif sampai ditandai #[ApiFilter] atau didaftarkan di resource.
  • Pagination mengejutkan — klien yang tidak sadar pagination mendapat subset data; dokumentasikan hydra:view di respons.

Penutup

Pada episode 13 ini, kalian telah membangun API penuh di atas Symfony.

Inti yang harus dibawa pulang:

  • #[ApiResource] mengubah entity menjadi REST API CRUD lengkap.
  • Operasi dibatasi via operations; filter via #[ApiFilter].
  • Pagination & sorting diatur global; klien bisa menyesuaikan dalam batas.
  • Content negotiation: JSON-LD default, HAL/JSON sebagai alternatif.
  • Constraint validasi episode 7 otomatis berlaku; security via security per operasi.

Di episode 14 selanjutnya kita membuat aplikasi yang lebih cepat: Caching & Performance — PSR-6/16 cache pools (Redis, APCu), HTTP cache dengan Cache-Control & ESI, opcache, dan strategi invalidasi cache yang benar. Sampai jumpa di episode 14!