API Platform GraphQL Symfony: Skema, Mutasi, dan Pertanyaan Interview 2026

Panduan lengkap integrasi API Platform GraphQL dengan Symfony. Pelajari skema otomatis, mutasi, resolver kustom, keamanan, dan pertanyaan interview teknis untuk developer 2026.

API Platform GraphQL Symfony: Skema, Mutasi, dan Pertanyaan Interview 2026

API Platform GraphQL mengubah aplikasi Symfony menjadi API yang powerful dan type-safe, menyelesaikan masalah over-fetching dan under-fetching yang umum terjadi pada REST. Dengan generasi skema otomatis dari atribut PHP dan dukungan penuh spesifikasi Relay, API Platform 4.x menyediakan implementasi GraphQL siap produksi dengan konfigurasi minimal.

Perbedaan Utama: GraphQL vs REST di API Platform

GraphQL meminta field yang dibutuhkan secara spesifik dalam satu query, sementara REST mengembalikan struktur respons yang tetap. API Platform menghasilkan kedua endpoint dari definisi resource yang sama, memungkinkan klien memilih protokol yang sesuai dengan kebutuhan.

Instalasi dan Aktivasi Dukungan GraphQL di Symfony

API Platform memisahkan fungsionalitas GraphQL ke dalam paket khusus. Pendekatan modular ini menjaga core tetap ringan untuk proyek yang hanya membutuhkan REST.

bash
# Install GraphQL support
composer require api-platform/graphql

Setelah terinstall, endpoint /graphql tersedia secara otomatis. Skema dihasilkan dari atribut #[ApiResource] yang ada tanpa konfigurasi tambahan.

src/Entity/Book.phpphp
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Mutation;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ApiResource(
    graphQlOperations: [
        new Query(),
        new QueryCollection(),
        new Mutation(name: 'create'),
        new Mutation(name: 'update'),
        new Mutation(name: 'delete'),
    ]
)]
class Book
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $title;

    #[ORM\Column(length: 13)]
    private string $isbn;

    #[ORM\Column]
    private \DateTimeImmutable $publishedAt;

    // Getters and setters...
}

Definisi entity tunggal ini mengekspos query untuk mengambil buku berdasarkan ID atau sebagai koleksi, ditambah mutasi untuk operasi create, update, dan delete. Skema GraphQL mencerminkan tipe PHP secara langsung: string menjadi String!, tipe nullable menjadi field opsional.

Query GraphQL menentukan field mana yang akan dikembalikan secara tepat. Presisi ini menghilangkan pemborosan bandwidth dan mengurangi transformasi data di sisi klien.

graphql
# Fetch a single book with specific fields
query GetBook {
  book(id: "/books/42") {
    title
    isbn
    publishedAt
  }
}

# Fetch a collection with pagination
query ListBooks {
  books(first: 10, after: "cursor123") {
    edges {
      node {
        id
        title
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Mutasi mengikuti spesifikasi Relay dengan objek input dan clientMutationId untuk pelacakan request.

graphql
# Create a new book
mutation CreateBook {
  createBook(input: {
    title: "Domain-Driven Design"
    isbn: "9780321125217"
    publishedAt: "2003-08-30"
    clientMutationId: "create-1"
  }) {
    book {
      id
      title
    }
    clientMutationId
  }
}

# Update an existing book
mutation UpdateBook {
  updateBook(input: {
    id: "/books/42"
    title: "Updated Title"
    clientMutationId: "update-1"
  }) {
    book {
      id
      title
    }
  }
}

clientMutationId membantu klien menghubungkan respons dengan request dalam skenario batch. API Platform mengembalikannya tanpa perubahan dalam respons.

Implementasi Custom Resolver untuk Logika Bisnis Kompleks

Operasi CRUD default mencakup kasus dasar, tetapi aplikasi nyata membutuhkan logika bisnis kustom. API Platform menyediakan interface resolver untuk query dan mutasi.

src/Resolver/BookBestSellerResolver.phpphp
namespace App\Resolver;

use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface;
use App\Repository\BookRepository;

final class BookBestSellerResolver implements QueryCollectionResolverInterface
{
    public function __construct(
        private readonly BookRepository $bookRepository
    ) {}

    /**
     * @param iterable<Book> $collection
     * @return iterable<Book>
     */
    public function __invoke(iterable $collection, array $context): iterable
    {
        // Access GraphQL arguments from context
        $limit = $context['args']['limit'] ?? 10;
        $period = $context['args']['period'] ?? 'month';

        return $this->bookRepository->findBestSellers($limit, $period);
    }
}

Daftarkan custom resolver dalam konfigurasi entity:

src/Entity/Book.phpphp
#[ApiResource(
    graphQlOperations: [
        new QueryCollection(
            name: 'bestSellers',
            resolver: BookBestSellerResolver::class,
            args: [
                'limit' => ['type' => 'Int', 'default_value' => 10],
                'period' => ['type' => 'String', 'default_value' => 'month'],
            ]
        ),
    ]
)]
class Book
{
    // ...
}

Konfigurasi ini mengekspos query bestSellers yang menerima argumen limit dan period, mengeksekusi logika repository kustom alih-alih query Doctrine default.

Siap menguasai wawancara Symfony Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Mengamankan Operasi GraphQL dengan Voter dan Expression

Konfigurasi keamanan untuk GraphQL beroperasi secara independen dari REST. Setiap operasi dapat mendefinisikan aturan akses sendiri menggunakan expression language Symfony.

src/Entity/Book.phpphp
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\Mutation;

#[ApiResource(
    graphQlOperations: [
        new Query(
            security: "is_granted('ROLE_USER')"
        ),
        new QueryCollection(
            security: "is_granted('ROLE_USER')"
        ),
        new Mutation(
            name: 'create',
            security: "is_granted('ROLE_EDITOR')"
        ),
        new Mutation(
            name: 'update',
            security: "is_granted('ROLE_EDITOR') and object.getAuthor() == user",
            securityMessage: "Only the author can update this book."
        ),
        new Mutation(
            name: 'delete',
            security: "is_granted('ROLE_ADMIN')"
        ),
    ]
)]
class Book
{
    // ...
}

Variabel object dalam security expression merujuk ke entity yang diakses. Ini memungkinkan pengecekan kepemilikan yang detail. Untuk logika otorisasi yang kompleks, Symfony Security voters menyediakan solusi yang lebih bersih daripada expression inline.

src/Security/Voter/BookVoter.phpphp
namespace App\Security\Voter;

use App\Entity\Book;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

class BookVoter extends Voter
{
    public const EDIT = 'BOOK_EDIT';
    public const DELETE = 'BOOK_DELETE';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return in_array($attribute, [self::EDIT, self::DELETE])
            && $subject instanceof Book;
    }

    protected function voteOnAttribute(
        string $attribute,
        mixed $subject,
        TokenInterface $token
    ): bool {
        $user = $token->getUser();
        if (!$user instanceof UserInterface) {
            return false;
        }

        /** @var Book $book */
        $book = $subject;

        return match($attribute) {
            self::EDIT => $book->getAuthor() === $user,
            self::DELETE => in_array('ROLE_ADMIN', $user->getRoles()),
            default => false,
        };
    }
}

Update Real-Time dengan GraphQL Subscription

API Platform mengimplementasikan subscription GraphQL melalui Mercure, protokol untuk server-sent events. Subscription mendorong data ke klien ketika resource berubah.

src/Entity/Book.phpphp
use ApiPlatform\Metadata\GraphQl\Subscription;

#[ApiResource(
    mercure: true,
    graphQlOperations: [
        new Query(),
        new Mutation(name: 'update'),
        new Subscription(),
    ]
)]
class Book
{
    // ...
}

Klien berlangganan perubahan menggunakan sintaks subscription GraphQL standar:

graphql
subscription BookUpdates {
  updateBookSubscribe(input: { id: "/books/42" }) {
    book {
      id
      title
      updatedAt
    }
  }
}

Ketika mutasi memperbarui buku, Mercure menyiarkan perubahan ke semua klien yang berlangganan. Pola ini cocok untuk aplikasi kolaboratif, dashboard real-time, dan notifikasi langsung. Untuk skenario throughput tinggi, kombinasikan subscription dengan Symfony Messenger untuk memisahkan pemrosesan mutasi dari pengiriman notifikasi.

Pertanyaan Interview: API Platform GraphQL

Interview teknis untuk posisi Symfony semakin banyak mencakup integrasi GraphQL. Pertanyaan-pertanyaan ini menguji pemahaman tentang spesifikasi dan implementasi API Platform.

T: Bagaimana API Platform menghasilkan skema GraphQL?

API Platform mengintrospeksi atribut #[ApiResource] dan deklarasi tipe PHP untuk membangun skema. Properti entity menjadi field, dengan tipe PHP dipetakan ke tipe GraphQL. Skema diregenerasi pada setiap request dalam mode development dan di-cache dalam produksi.

T: Apa perbedaan antara operasi Query dan QueryCollection?

Query mengambil satu item berdasarkan identifier dan membutuhkan argumen id. QueryCollection mengembalikan beberapa item dengan filtering, pagination, dan sorting opsional. Keduanya dapat memiliki custom resolver, tetapi interface-nya berbeda: QueryItemResolverInterface vs QueryCollectionResolverInterface.

T: Bagaimana menangani masalah query N+1 di API Platform GraphQL?

Pola DataLoader mengelompokkan beberapa query database menjadi satu. API Platform terintegrasi dengan eager loading Doctrine melalui fetch join di query extension. Untuk kasus kompleks, implementasikan custom resolver yang menggunakan addSelect() Doctrine untuk mengambil asosiasi dalam query awal.

php
// Custom query extension for eager loading
public function applyToCollection(
    QueryBuilder $queryBuilder,
    QueryNameGeneratorInterface $queryNameGenerator,
    string $resourceClass,
    ?Operation $operation = null,
    array $context = []
): void {
    $queryBuilder
        ->addSelect('author')
        ->leftJoin('o.author', 'author');
}

T: Bisakah aturan keamanan REST dan GraphQL berbeda untuk resource yang sama?

Ya. Operasi REST menggunakan security pada #[Get], #[Post], dll., sementara operasi GraphQL menggunakan security pada #[Query], #[Mutation], dll. Pemisahan ini memungkinkan aturan yang lebih ketat untuk satu protokol. Pola umum adalah mengekspos GraphQL read-only untuk klien publik sementara mutasi REST memerlukan autentikasi.

T: Bagaimana menambahkan tipe scalar kustom ke skema GraphQL?

Daftarkan tipe kustom di config/packages/api_platform.yaml dan implementasikan logika serialisasi:

yaml
# config/packages/api_platform.yaml
api_platform:
    graphql:
        enabled: true
        graphiql:
            enabled: true
src/GraphQL/Type/DateTimeType.phpphp
namespace App\GraphQL\Type;

use GraphQL\Type\Definition\ScalarType;

final class DateTimeType extends ScalarType
{
    public string $name = 'DateTime';

    public function serialize($value): string
    {
        return $value->format(\DateTimeInterface::RFC3339);
    }

    public function parseValue($value): \DateTimeImmutable
    {
        return new \DateTimeImmutable($value);
    }

    public function parseLiteral($valueNode, ?array $variables = null): \DateTimeImmutable
    {
        return new \DateTimeImmutable($valueNode->value);
    }
}

Strategi Pagination: Cursor vs Page-Based

API Platform default menggunakan pagination berbasis cursor mengikuti spesifikasi Relay Connection. Pendekatan ini menangani data real-time lebih baik daripada pagination offset karena penyisipan tidak menggeser hasil.

graphql
# Cursor-based (default)
query {
  books(first: 10, after: "YXJyYXljb25uZWN0aW9uOjk=") {
    edges {
      cursor
      node {
        title
      }
    }
    pageInfo {
      endCursor
      hasNextPage
    }
  }
}

Pagination berbasis halaman cocok untuk kasus penggunaan yang lebih sederhana di mana klien membutuhkan akses halaman langsung:

php
// Enable page-based pagination
#[ApiResource(
    paginationType: 'page',
    graphQlOperations: [
        new QueryCollection(paginationType: 'page'),
    ]
)]
class Book {}
graphql
# Page-based
query {
  books(page: 2, itemsPerPage: 20) {
    collection {
      title
    }
    paginationInfo {
      totalCount
      lastPage
    }
  }
}

Pagination cursor berkinerja lebih baik dalam skala karena menghindari query OFFSET. Trade-off-nya adalah klien tidak dapat melompat ke halaman sembarang.

Testing Endpoint GraphQL di Symfony

Test fungsional memverifikasi perilaku GraphQL menggunakan test client Symfony. API Platform menyediakan trait test khusus GraphQL.

tests/GraphQL/BookTest.phpphp
namespace App\Tests\GraphQL;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;

class BookTest extends ApiTestCase
{
    public function testQueryBook(): void
    {
        $client = static::createClient();

        // Create test data
        $book = new Book();
        $book->setTitle('Test Book');
        $book->setIsbn('1234567890123');
        $book->setPublishedAt(new \DateTimeImmutable());

        $em = static::getContainer()->get('doctrine')->getManager();
        $em->persist($book);
        $em->flush();

        // Execute GraphQL query
        $response = $client->request('POST', '/graphql', [
            'json' => [
                'query' => '
                    query GetBook($id: ID!) {
                        book(id: $id) {
                            title
                            isbn
                        }
                    }
                ',
                'variables' => [
                    'id' => '/books/' . $book->getId(),
                ],
            ],
        ]);

        $this->assertResponseIsSuccessful();
        $data = $response->toArray();

        $this->assertEquals('Test Book', $data['data']['book']['title']);
        $this->assertEquals('1234567890123', $data['data']['book']['isbn']);
    }

    public function testMutationRequiresAuthentication(): void
    {
        $client = static::createClient();

        $response = $client->request('POST', '/graphql', [
            'json' => [
                'query' => '
                    mutation CreateBook {
                        createBook(input: {
                            title: "Unauthorized Book"
                            isbn: "0000000000000"
                            clientMutationId: "test"
                        }) {
                            book { id }
                        }
                    }
                ',
            ],
        ]);

        $data = $response->toArray();
        $this->assertArrayHasKey('errors', $data);
    }
}

Test ini memvalidasi operasi yang berhasil dan penegakan keamanan. Jalankan dengan php bin/phpunit tests/GraphQL/ untuk menangkap regresi dalam perilaku API.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Poin Penting API Platform GraphQL di Symfony

  • Install api-platform/graphql untuk mengaktifkan endpoint /graphql dengan generasi skema otomatis dari atribut #[ApiResource]
  • Gunakan operasi Query, QueryCollection, dan Mutation untuk mengontrol operasi GraphQL mana yang diekspos setiap resource
  • Implementasikan QueryItemResolverInterface atau MutationResolverInterface untuk logika bisnis kustom di luar CRUD
  • Security expression pada operasi GraphQL bekerja independen dari REST, memungkinkan aturan akses berbeda per protokol
  • Aktifkan Mercure untuk subscription real-time yang mendorong perubahan ke klien saat resource diperbarui
  • Pagination berbasis cursor menangani data real-time lebih baik daripada berbasis halaman, tetapi mengorbankan akses halaman langsung
  • Test endpoint GraphQL dengan ApiTestCase dan request JSON POST ke /graphql
  • Pertanyaan interview fokus pada generasi skema, masalah N+1, pemisahan keamanan, dan custom resolver
Tantangan harian

Bisakah kamu menemukan bug di Symfony?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 27 Agustus 2026

Bagikan

Artikel terkait