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.
Table of Contents
- 1. What API Platform GraphQL Generates Automatically
- 2. Basic Configuration: Enabling GraphQL in API Platform
- 3. Strengths: Where the Automatic Approach Really Helps
- 4. Limits: Where the Automation Fails
- 5. Custom Resolvers and Mutations in API Platform
- 6. The N+1 Problem With Doctrine and API Platform
- 7. API Platform vs. Handcrafted GraphQL: The Comparison
- 8. Comparison With Magento GraphQL
- 9. Decision Tree: Which Approach When?
- 10. Summary
- 11. FAQ
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.