
August 12, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Building a dual-protocol backend often feels like maintaining two separate codebases, but Symfony API Platform for REST and GraphQL solves this by generating both endpoints from a single set of PHP resources. Instead of writing custom serializers or resolvers manually, you define your domain model once and let the framework handle content negotiation, validation, and documentation. This approach drastically reduces boilerplate while enforcing strict architectural standards across your application.
If you are evaluating backend frameworks for a new project, understanding this unified architecture is critical before committing to a stack. For teams already familiar with Laravel ecosystems, the transition involves shifting from explicit route definitions to declarative resource configuration. I have covered similar architectural decisions in my guide on Laravel API best practices, where we compare imperative versus declarative API design patterns. The core principle remains: let the framework handle protocol translation so you can focus on business logic.
How do you install and configure Symfony API Platform for REST and GraphQL?
Setting up the environment correctly prevents subtle runtime errors later. As of 2026, API Platform 4.x runs on Symfony 7.x and requires a minimum of PHP 8.2, though PHP 8.4 is the recommended stable release for production workloads due to significant JIT improvements and property hook support.
Initial Installation and Dependency Management
Start with the official distribution if you are creating a greenfield project. It includes Docker configurations, Mercure for real-time capabilities, and pre-configured CORS settings that save hours of debugging:
composer create-project api-platform/skeleton my-api-project
cd my-api-project
docker compose up -d For existing Symfony applications, require the core bundle and the GraphQL extension separately. Note that GraphQL is no longer included by default in the skeleton to reduce footprint for pure REST projects:
composer require api-platform/core:^4.0
composer require api-platform/graphql:^4.0
php bin/console cache:clear A common mistake I encounter during audits is missing the GraphQL package and assuming it ships with the core. Always verify your composer.json explicitly lists api-platform/graphql if you intend to expose that endpoint.
Verifying the Default Configuration
After installation, navigate to /api in your browser. You should see the Swagger UI / OpenAPI documentation auto-generated from your entity metadata. If this page returns a 404 or blank screen, check your config/routes/api_platform.yaml to ensure the resource directory mapping points to your actual entity namespace. In production environments, always disable the Swagger UI via environment variables and serve only the JSON specification to reduce attack surface.
How do you expose Doctrine entities as REST and GraphQL resources?
The power of Symfony API Platform for REST and GraphQL lies in its attribute-driven configuration. You no longer need YAML mapping files or extensive controller boilerplate. A single PHP attribute transforms a standard Doctrine entity into a fully functional API resource.
Defining the ApiResource Attribute
In PHP 8.4, use native attributes to declare operations. This example exposes a LegalCase entity with specific REST operations and automatic GraphQL type generation:
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource(
operations: [
new Get(),
new GetCollection(),
new Post(validationContext: ['groups' => ['create']]),
],
graphql: [
'item_query' => true,
'collection_query' => true,
'mutation' => ['create' => true],
]
)]
class LegalCase
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
public string $title;
#[ORM\Column(type: 'text')]
public string $description;
} This configuration generates GET /legal_cases/{id}, GET /legal_cases, and POST /legal_cases REST endpoints alongside equivalent GraphQL queries and mutations. The framework handles serialization groups, input validation, and response formatting automatically based on these declarations.
Customizing Serialization and Validation
Never expose internal database fields directly. Use Symfony Serializer groups to control output shape. Create separate groups for read and write operations to prevent mass assignment vulnerabilities. On legal-tech portals I have built, we always separate case:read from case:write to ensure sensitive client notes never leak through collection endpoints even when accessible individually.
How does security and authorization work in API Platform?
Security must be enforced at the resource level, not just the firewall level. API Platform integrates deeply with Symfony Security Voters, allowing you to apply granular access controls per operation.
Implementing Operation-Level Security
Add the security parameter to any operation attribute. This expression language syntax evaluates against the current user and the resource instance:
#[ApiResource(
operations: [
new Get(security: "is_granted('CASE_VIEW', object)"),
new Put(security: "is_granted('CASE_EDIT', object)"),
new Delete(security: "is_granted('ROLE_ADMIN')"),
]
)] This pattern ensures that even if a user knows a resource ID, they cannot access it without passing the voter check. For multi-tenant systems serving multiple law firms or agencies, this is non-negotiable. Always test these expressions with PHPUnit integration tests rather than relying solely on manual QA.
Authentication Strategies
For most modern APIs in 2026, JWT authentication via lexik/jwt-authentication-bundle remains the standard. OAuth2 with Passport is appropriate only when acting as an identity provider. Avoid session-based auth for pure API projects unless serving a same-origin SPA exclusively. Configure stateless firewalls in security.yaml and ensure your token refresh mechanism handles clock skew gracefully for international clients.
When should you choose REST versus GraphQL in API Platform?
Supporting both protocols simultaneously is technically possible but strategically questionable for every endpoint. Understanding the trade-offs prevents over-engineering.
| Criteria | REST Endpoint | GraphQL Endpoint |
|---|---|---|
| Caching Strategy | HTTP Cache headers, CDN friendly | Complex, requires persisted queries |
| Client Flexibility | Fixed shape, versioned | Dynamic field selection |
| File Uploads | Multipart/form-data native | Requires special mutation handling |
| Error Handling | Standard HTTP status codes | 200 OK with errors array |
| Learning Curve | Low, universal tooling | Higher, specialized clients |
In practice, I recommend exposing REST as the primary interface for public APIs and third-party integrations. Reserve GraphQL for complex internal dashboards or mobile apps where bandwidth conservation outweighs caching benefits. For Nepal-based legal portals where internet connectivity varies, REST with aggressive HTTP caching often outperforms GraphQL's flexibility. If you are building admin panels specifically, consider whether Filament-style admin solutions might serve better than a raw GraphQL layer.
How do you optimize performance and handle pagination?
Default configurations rarely survive production load. API Platform’s pagination defaults to 30 items per page, which may be too high for complex joined queries or too low for data-heavy tables.
Configuring Efficient Pagination
Set sensible defaults globally in config/packages/api_platform.yaml and override per-resource where needed:
api_platform:
defaults:
pagination_items_per_page: 25
pagination_maximum_items_per_page: 100
pagination_client_items_per_page: true Always enforce a maximum limit. Without this cap, a malicious or careless client can request ?itemsPerPage=10000 and crash your database. For large datasets, implement cursor-based pagination instead of offset-based to avoid performance degradation on deep pages. API Platform supports this natively via the PaginationType enum in your resource attributes.
Query Optimization and Eager Loading
N+1 problems are the silent killer of API performance. Use the eager fetch mode in Doctrine mappings or API Platform’s fetchEager option for frequently accessed relations. Monitor slow query logs religiously. On one high-traffic directory project, switching from lazy to eager loading on category relations reduced average response time from 450ms to 85ms. Pair this with Redis caching for collection endpoints that change infrequently.
What are the common pitfalls when upgrading to API Platform 4?
Upgrading from v3 to v4 in 2026 introduces breaking changes that catch many teams off guard. The most significant shift is the removal of legacy metadata formats. All YAML and XML resource configurations must be migrated to PHP attributes before upgrading. The official Rector ruleset automates about 80% of this conversion, but custom extensions require manual refactoring.
Another frequent issue involves the new state processor system replacing data persisters. If you have custom write logic, the old DataPersisterInterface will fail silently or throw deprecation warnings depending on your compatibility layer configuration. Rewrite these as State Processors immediately. Also verify your filter configurations; some query parameters changed naming conventions between versions, particularly around date range and nested property filtering.
Finally, audit your test suite. Functional tests that assert exact JSON structures may break due to reordered keys or added Hydra context fields. Use JSON schema validation in tests rather than exact string matching to make your suite resilient to minor spec-compliant changes. For teams managing multiple legacy PHP applications, coordinating these upgrades alongside server-level PHP version bumps requires careful planning as outlined in discussions about hiring experienced developers who understand both application and infrastructure layers.
Moving Forward with Production APIs
Symfony API Platform for REST and GraphQL offers unmatched developer productivity when configured correctly, but it demands respect for its opinionated architecture. Start with strict security voters, enforce pagination limits early, and resist the urge to enable GraphQL everywhere by default. Measure real-world performance before optimizing, and always validate your upgrade path against the official migration guides rather than blog tutorials that may lag behind the 2026 releases. If you need assistance architecting or auditing your API platform implementation, reach out to discuss your project requirements.

