GraphQL in Symfony with API Platform: When It's Enough and When It Isn't
AI generated
{ }
type
GraphQL · Symfony · API Platform · PHP · Schema Generation
GraphQL in Symfony with API Platform:
When It's Enough and When It Isn't

API Platform automatically generates GraphQL schemas from PHP entities, which sounds like the simplest possible solution. In practice, though, the limits show up quickly: complex mutations, domain-specific validation, N+1 performance problems, and a lack of control over the generated schema. This article shows when API Platform GraphQL is enough, and when it needs to be extended or replaced.

18 min read API Platform · Custom Resolvers · N+1 · Schema Control Symfony · PHP · GraphQL · Magento Comparison

1. What API Platform GraphQL Generates Automatically

API Platform is a Symfony framework for building API-first applications. With its optional GraphQL support (enabled through the api-platform/graphql package), a complete GraphQL schema is generated automatically from annotated PHP classes. For every API resource, the following are created automatically: a query for a single object, a query for collections with pagination, mutations for create, update, and delete, plus all the necessary input and connection types. That is a considerable speed advantage for simple CRUD APIs.

The schema generation is based on Doctrine entities or other PHP classes annotated with the #[ApiResource] attribute and, optionally, #[ApiProperty] attributes. Fields can be included or excluded individually, and serialization groups control which data is visible in which contexts. The result is a GraphQL schema that stays in sync with the data model, without having to maintain manual SDL files. For quick prototypes, internal tools, and simple CRUD scenarios, that is a real advantage.

2. Basic Configuration: Enabling GraphQL in API Platform

Enabling GraphQL in API Platform requires only a few configuration steps. The GraphQL package is installed via Composer, GraphQL is enabled in the API Platform configuration, and GraphiQL can optionally be turned on for interactive schema exploration. From that point on, every #[ApiResource] class is automatically included in the GraphQL schema. The default operations, query and mutation for every resource, are provided without any further configuration. Additional GraphQL operations can be configured directly on the class via attributes.

One important difference from manually written GraphQL schemas: in API Platform, the schema is always a mirror of the data model. Schema design decisions that diverge from the data model require explicit configuration or custom resolvers. That is simultaneously its biggest strength (automatic consistency) and its biggest weakness (limited control over the exposed schema). Anyone who strictly prefers domain-oriented schema design, where the schema is defined independently of the database model, quickly runs into the limits of the automatic approach.


# Auto-generated schema from API Platform, based on PHP Entity annotations
# This is what API Platform generates from #[ApiResource] classes

type Query {
  product(id: ID!): Product
  products(
    page: Int
    itemsPerPage: Int
    order: ProductFilter_order
    name: String
    name_list: [String]
    price: Float
    price_between: [Float]
  ): ProductCollection
}

type Mutation {
  createProduct(input: createProductInput!): createProductPayload
  updateProduct(input: updateProductInput!): updateProductPayload
  deleteProduct(input: deleteProductInput!): deleteProductPayload
}

type Product {
  id: ID!
  name: String!
  description: String
  price: Float!
  category: Category
  createdAt: String!
}

# API Platform generates Connection types for pagination
type ProductCollection {
  collection: [Product]
  paginationInfo: ProductPaginationInfo!
}

3. Strengths: Where the Automatic Approach Really Helps

API Platform GraphQL is especially strong for internal administrative applications, where the data model and the GraphQL schema sit close together. An admin backend for content management, an internal dashboard for order management, or an API for mobile apps that only needs CRUD operations: in these scenarios, the automatic approach is genuinely productive. The team only maintains PHP classes, and the schema updates itself automatically. New fields on the entity appear in the schema immediately, without any manual SDL update.

API Platform GraphQL is also valuable for rapid API prototypes in early project phases. When the data model is still in flux and the team is iterating quickly, the automatic approach avoids maintaining PHP code and a GraphQL schema in parallel. Once the project matures and the schema needs to stabilize, you can move from automatic generation toward more manual control without changing the overall architecture. API Platform allows for this gradual transition through custom resolvers and explicit schema configuration.

4. Limits: Where the Automation Fails

The first limit shows up with complex domain-specific mutations. A mutation like "place an order" is not a simple create on an Order entity. It involves stock checks, price calculation, payment initialization, sending emails, and inventory reservation. API Platform can generate a createOrder mutation, but the business logic behind it has to be implemented in a custom state processor. The more complex the business logic, the more configuration and custom code is required, until the advantage of the automation fades away.

The second limit is lack of control over the generated schema. API Platform generates schema names, types, and structures based on PHP class names and annotations. The result often does not match the domain-oriented schema design that frontend teams expect. Field names that diverge from PHP conventions (camelCase), connection types that are too complex for simple cases, and automatically generated filters that are not self-explanatory for frontend developers: all of that requires additional configuration, which eats back into the initial time savings.


# Custom mutation in API Platform, for when auto-generated CRUD is not enough
# Implemented via a custom MutationResolver class

type Mutation {
  # Auto-generated, simple CRUD
  createProduct(input: createProductInput!): createProductPayload

  # Custom mutation, complex domain operation
  placeOrder(input: PlaceOrderInput!): PlaceOrderPayload!
  applyDiscountCode(input: ApplyDiscountInput!): ApplyDiscountPayload!
  processReturn(input: ProcessReturnInput!): ProcessReturnPayload!
}

input PlaceOrderInput {
  clientMutationId: String
  cartId: ID!
  shippingMethodCode: String!
  paymentMethodCode: String!
  billingAddressId: ID!
  shippingAddressId: ID
}

type PlaceOrderPayload {
  clientMutationId: String
  order: Order
  userErrors: [PlaceOrderError!]!
}

type PlaceOrderError {
  code: PlaceOrderErrorCode!
  message: String!
  field: String
}

enum PlaceOrderErrorCode {
  OUT_OF_STOCK
  PAYMENT_FAILED
  INVALID_ADDRESS
  COUPON_EXPIRED
}

5. Custom Resolvers and Mutations in API Platform

API Platform offers two extension points for custom logic: state providers for queries and state processors for mutations. A state provider implements the data access logic for a given operation and replaces or supplements the automatic Doctrine query. A state processor implements the execution logic for create, update, and delete operations. Both are implemented as PHP classes configured through dependency injection.

For fully custom mutations that have no counterpart in a Doctrine entity, you can configure the graphQlOperations attribute with a Mutation operation and a custom resolver. The custom resolver is a PHP class implementing the MutationResolverInterface. This flexibility makes it possible to use API Platform as a framework and rely on automatic generation only where it fits, while implementing complex operations entirely by hand. The result is often a hybrid approach: simple CRUD operations automatic, complex domain operations manual.

6. The N+1 Problem With Doctrine and API Platform

Doctrine, the default ORM in Symfony, loads related entities lazily by default. In a GraphQL context, that means: when a collection query returns a list of products and each product exposes its related category, Doctrine issues a separate SQL query for the category for every single product. With 20 products, that produces 21 queries instead of one. This N+1 problem occurs automatically whenever API Platform exposes Doctrine entities without specific query optimization.

API Platform and Doctrine offer several ways to address this. With eager loading via fetch="EAGER" or explicit JOIN FETCH queries in a custom state provider, related entities are loaded in a single query. The Doctrine DataLoader collects all requested IDs within a single tick and resolves them in one batch query. For more complex scenarios, a query builder in a custom state provider can formulate exactly the optimal query. The result, however, is always more manual code, and the advantage of automatic generation shrinks as the complexity of the relationships grows.

7. API Platform vs. Handcrafted GraphQL: The Comparison

The alternative to API Platform in Symfony is a fully manual GraphQL implementation using the webonyx/graphql-php library. In that setup, SDL schema files are defined manually, resolver classes are implemented manually, and the entire request lifecycle is configured manually. That requires more upfront effort but offers complete control over every detail of the schema and the execution logic. A handcrafted GraphQL schema is often more cleanly domain-oriented, easier for frontend teams to understand, and has better performance, because resolver chains can be optimized specifically.

Criterion API Platform GraphQL Handcrafted GraphQL Winner
Time to first result Very fast (automatic) Slower (manual) API Platform
Schema control Limited Complete Handcrafted
N+1 performance Problematic (lazy loading) Controllable Handcrafted
Complex mutations Possible, but costly Natural Handcrafted
Maintainability at scale Declines Stays controllable Handcrafted

The decision is not binary. A hybrid approach makes sense for many projects: API Platform for simple CRUD operations that genuinely only expose the data model, and handcrafted custom resolvers for complex domain operations. That combines the speed of automation with the control of the manual approach.

8. Comparison With Magento GraphQL

Magento's GraphQL implementation is fully handcrafted: SDL files define the schema, resolver classes implement the execution logic, and the dependency injection container connects the two. That means complete control over every detail of the schema, paid for with more manual work. Anyone building a custom Magento module with GraphQL support writes SDL files, resolver classes, and wires up the resolver bindings in di.xml. That is more precise and more performant than API Platform, but more expensive to build initially.

In projects that use both Symfony and Magento, the question sometimes comes up which approach fits which part of the application. Magento handles commerce-specific logic (catalog, checkout, customer management) optimally with its handcrafted GraphQL. Symfony with API Platform can make sense for adjacent services, for example a CMS service or a B2B portal, where simpler CRUD operations dominate and development speed matters more than full schema control.


# Comparison: Magento handcrafted vs. API Platform auto-generated

# Magento: explicit SDL with domain-specific naming and types
# File: etc/schema.graphqls in custom module
type Query {
  customOrders(
    filter: CustomerOrdersFilterInput
    sort: CustomerOrderSortInput
    pageSize: Int = 20
    currentPage: Int = 1
    scope: ScopeTypeEnum
  ): CustomerOrders
}

type CustomerOrders {
  items: [CustomerOrder]!
  page_info: SearchResultPageInfo!
  total_count: Int!
}

# API Platform: auto-generated, Doctrine-coupled naming
# From: #[ApiResource] class Order {}
type Query {
  order(id: ID!): Order
  orders(
    page: Int
    itemsPerPage: Int
    order: OrderFilter_order
    status: String
    customer_id: Int
  ): OrderCollection
}
# Note: naming follows PHP class structure, not domain language

9. Decision Tree: Which Approach When?

The choice between API Platform GraphQL and handcrafted GraphQL depends on several factors. API Platform is the right choice when the project mainly exposes CRUD operations on Doctrine entities, the domain logic is simple, and the schema structure can stay close to the data model. Typical scenarios: internal admin tools, simple REST-to-GraphQL migrations, and prototypes. In these cases, automatic generation saves considerable time and reduces boilerplate.

Handcrafted GraphQL is the better choice when the project has complex domain operations that go beyond simple CRUD, when the schema needs to be deliberately optimized for frontend developers, when performance optimizations in resolver chains are required, or when the schema needs to evolve independently of the database model. In commerce projects, where mutations like "place an order" or "request a return" coordinate many services, the handcrafted approach is almost always the right choice, regardless of whether Magento or a custom Symfony project is used.

10. Summary

API Platform GraphQL solves one specific problem very well: it makes CRUD APIs with GraphQL productive in a short amount of time. Where the schema can directly mirror the data model, where the business logic is simple, and where development speed matters more than schema control, API Platform saves significant time. The limits show up with complex domain operations, with the N+1 problem in Doctrine, with the lack of control over schema naming and structure, and when the schema needs to grow independently of the database model.

The practical recommendation: use API Platform as a starting point for new Symfony projects and extend it selectively with custom resolvers and state processors where the automation is not enough. For commerce projects or systems with complex domain logic, invest in handcrafted GraphQL from the start. The Magento approach, fully handcrafted, complete schema control, resolvers delegating to service classes, is the more mature but more costly alternative, one that pays off as complexity grows.

GraphQL in Symfony With API Platform: The Key Takeaways

Strengths

Fast start, automatic schema synchronization, little boilerplate for CRUD operations and simple domains.

Limits

N+1 with Doctrine, limited schema control, complex mutations require custom code, schema follows PHP structure.

Hybrid Approach

API Platform for CRUD, custom resolvers for domain operations. Both can coexist in the same project.

Vs. Magento GraphQL

Magento: complete control, handcrafted, optimized for commerce. API Platform: faster start, less control.

11. FAQ: GraphQL in Symfony With API Platform

1What does API Platform generate automatically?
Queries, mutations, input types and connection types from #[ApiResource] entities. CRUD fully automatic, without having to write SDL files.
2When is API Platform not enough?
With complex domain operations, N+1 performance problems, missing schema control, and when the schema needs to be independent of the database model.
3How do you implement custom mutations?
State processors for CRUD with custom logic, MutationResolver classes for fully custom mutations with no direct entity counterpart.
4How do you solve N+1 with Doctrine?
Eager loading for simple cases, a custom state provider with JOIN FETCH for more complex queries, or a Doctrine DataLoader for batch loading.
5Combine API Platform with handcrafted GraphQL?
Yes. API Platform for CRUD, custom resolvers for domain operations. Both coexist in the same Symfony project.
6Difference from Magento GraphQL?
Magento: fully handcrafted, maximum control, optimized for commerce. API Platform: automatic, faster start, less control over schema details.
7Control schema naming in API Platform?
#[ApiProperty] with a name parameter, serialization groups, or a custom NameConverter. Complete control like in handcrafted GraphQL is hard to achieve.
8Is API Platform suited for commerce projects?
For simple B2B portals and internal tools: yes. For complex checkout, pricing and inventory: usually not. Handcrafted GraphQL is better suited there.
9Is API Platform production-ready?
Yes. The question is for what: for simple CRUD APIs, excellent. For complex domains, it requires substantial custom code effort.
10How do I get started in an existing Symfony project?
Install api-platform/graphql, enable API Platform, annotate an entity with #[ApiResource]. The first schema is available within a few minutes.