Performance, Testing, Security, Tooling
A GraphQL API that works in development mode is not yet a production-ready API. Performance traps, security gaps and missing observability only surface under load, exactly when there is little room to react. This checklist closes those gaps before they become a problem.
Table of Contents
- 1. What sets a production-ready GraphQL API apart from a development API
- 2. Performance: the most important measures
- 3. Security: closing attack vectors systematically
- 4. Testing: queries, contracts and integration tests
- 5. Tooling: what is indispensable in production
- 6. Monitoring and observability
- 7. Magento-specific additions
- 8. Checklist: what must be in place before go-live
- 9. Summary
- 10. At a glance
- 11. FAQ
1. What sets a production-ready GraphQL API apart from a development API
During development, a GraphQL API is often deliberately configured wide open: introspection active, arbitrary queries allowed, no complexity limit, a query log capturing every request. That makes development work considerably easier. In production, the same settings are a security risk and a performance problem. Introspection gives attackers a complete view of the schema; unlimited query complexity permits DoS-like requests; a full query log under load costs CPU and memory.
The difference is not only a matter of configuration values, it is a systematic approach: performance is measured, not assumed, security is enforced through configuration rather than trust in clients, and test coverage includes not only happy paths but also error paths and edge cases. This checklist structures the most important measures into four areas: performance, security, testing and tooling.
2. Performance: the most important measures
N+1 problems are the most common source of performance issues in GraphQL APIs. The pattern is always the same: a list loads N objects, and each object triggers a separate database query for a nested field, instead of a single batch query for all N IDs at once. The solution is a DataLoader pattern: collect IDs, load them in a single database call and distribute the results to the waiting resolvers. In Magento this applies in particular to EAV attributes, which quickly become a bottleneck for product lists with a large pageSize value.
Query depth limits and complexity limits are the second pillar of performance. Without limits, any client can send a query with ten levels of nesting that blocks the server for seconds. A depth limit of 10 and a complexity budget of 200 (with field-specific costs) are sensible starting values for production APIs. Response caching at the resolver level for expensive, rarely changing data is the third measure, combined with CDN caching via persisted queries for publicly accessible data without user context.
# WRONG: Triggers N+1 (related_products resolver fires once per product)
query SlowProductList {
products(search: "jacket", pageSize: 20) {
items {
sku
name
related_products { # Resolver called 20 times, 20 DB queries
sku
name
}
}
}
}
# RIGHT: Fetch only what's needed (no nested resolver explosion)
query FastProductList {
products(search: "jacket", pageSize: 20) {
items {
sku
name
url_key
price_range {
minimum_price {
final_price { value currency }
}
}
}
}
}
3. Security: closing attack vectors systematically
Introspection should be disabled in production, or restricted to authenticated developers. Introspection makes it possible to query the entire schema, all types, fields, arguments and deprecations, programmatically. That makes development easier, but it also makes reconnaissance attacks easier. In production APIs, introspection should only be accessible for known IP ranges or with specific auth, not for all anonymous clients.
Depth limits, complexity limits and query whitelisting via persisted queries are the next layer of protection. Mutation-specific rate limits prevent attackers from sending hundreds of login attempts through a login mutation. Field-level authorization ensures that fields carrying sensitive data are only visible to authorized users, and this should be reflected not only in the resolvers but also in the schema definition itself. Without an explicit authentication check in the resolver, every field of the schema is accessible to every client.
# Security check: introspection query, should be blocked in production
query IntrospectionCheck {
__schema {
types {
name
fields {
name
type { name }
}
}
}
}
# Expected production response:
# { "errors": [{ "message": "GraphQL introspection is not allowed" }] }
# Correct: lean query with only required fields
query SecureProductQuery {
products(search: "jacket", pageSize: 10) {
total_count
items {
sku
name
price_range {
minimum_price {
final_price { value currency }
}
}
}
}
}
4. Testing: queries, contracts and integration tests
GraphQL testing follows the same layers as any API testing, but has its own particularities. Unit tests check individual resolver classes in isolation. Integration tests send real GraphQL queries against a test database and verify the full response. Contract tests make sure frontend queries remain valid against the current schema, this is the most important safeguard against breaking changes. A schema diff tool such as GraphQL Inspector shows, on every commit, whether a schema change breaks existing queries.
Snapshot tests for query responses are especially useful for complex, deeply nested response structures. Once captured, any deviation signals a potentially unintended change in resolver behavior. Negative tests, error responses for incorrect auth, aborts when complexity is too high, correct error messages for invalid input validation, are often underrepresented in practice, and yet they cover a large share of the error paths that matter most in production.
5. Tooling: what is indispensable in production
GraphQL Inspector is the most important tooling investment for teams: it detects breaking changes in the schema automatically and can be integrated into the CI pipeline as a GitHub Action. Every pull request that changes the schema automatically gets a diff report, with breaking and non-breaking changes clearly flagged. This replaces manual schema reviews for the most common cases and protects against unintended breaking changes made under time pressure.
Altair and GraphiQL are indispensable for local development: syntax highlighting, auto-completion against the schema, history, a variable editor and response formatting. Apollo Studio or GraphQL Hive take on the schema registry role in a team context: they track schema versions, detect which queries are actively used in production, and warn when a field is about to be deprecated while it is still in use. Anyone who wants to optimize queries without knowing how they are actually used in production is working blind.
# Contract test: validate this query against current schema before merge
# Run with: graphql-inspector validate schema.graphql queries/product-list.graphql
query ProductListContractTest(
$search: String!
$pageSize: Int = 20
$currentPage: Int = 1
) {
products(
search: $search
pageSize: $pageSize
currentPage: $currentPage
) {
total_count
page_info {
current_page
page_size
total_pages
}
items {
__typename
sku
name
url_key
price_range {
minimum_price {
final_price { value currency }
}
}
}
}
}
6. Monitoring and observability
GraphQL monitoring differs from REST monitoring in that every request hits the same endpoint (/graphql). Classic URL-based monitoring therefore does not show a meaningful picture, all you see is a single route with mixed latencies. Correct observability for GraphQL requires capturing the operation name (operationName in the request) as a dimension in metrics and traces. Only then can you see, per query, which one is slow, which one is used frequently and which one throws errors.
Structured logging with fields for operation_name, duration_ms, complexity, depth and error_count enables aggregated analysis. Tracing integration (OpenTelemetry, Jaeger) makes resolver call paths visible and shows which resolvers consume how much runtime. P95 and P99 latency per operation are the most relevant performance metrics, not averages, which hide outliers.
7. Magento-specific additions
In Magento GraphQL, a few checklist items are especially important. First: do not query total_count unnecessarily, it triggers a separate COUNT query that can be expensive with complex filter structures. When infinite scroll is used, total_count is often unnecessary. Second: avoid pageSize values above 100, Magento caps pageSize internally at a maximum of 300, but already at 100 the returned data volume and the EAV load can be significant.
Third: understand and use Magento GraphQL caching via X-Magento-Cache-Id. Unauthenticated queries receive a cache ID header that Varnish uses as a cache key. If the frontend sends additional headers that are not part of the cache key, responses will not be cached. Fourth: never carry Magento's developer mode configuration into production, developer mode disables GraphQL validation and caching and can lead to misleading performance results.
8. Checklist: what must be in place before go-live
The following table structures the most important points of the production checklist by priority and category.
| Area | Measure | Priority | Magento context |
|---|---|---|---|
| Security | Disable introspection or restrict it to auth | Critical | Magento: enable production mode |
| Security | Set query depth limit and complexity limit | Critical | Magento has its own complexity validation |
| Performance | Check for N+1 problems with profiling | Critical | EAV attributes especially vulnerable |
| Testing | Contract tests for all frontend queries | High | Integrate GraphQL Inspector into CI |
| Monitoring | Operation name as a metric dimension | High | Magento New Relic integration |
9. Summary
Production-ready GraphQL APIs differ from development APIs not primarily in the schema or the resolver logic, but in the systematic hardening against performance problems, security gaps and observability deficits. N+1 detection through profiling, complexity limits through configuration, contract tests through CI integration, and monitoring through operation-level dimensioning, these are the four pillars that lift an API from development status into production status.
For Magento GraphQL, the additional rules are: enable production mode, minimize EAV queries, cap pageSize values, understand the CDN caching configuration, and only query total_count when it is genuinely needed. Anyone who works through this checklist before go-live builds the foundation for a GraphQL API that stays stable, secure and observable even under load.
Production-Ready GraphQL Checklist, the essentials at a glance
Security (mandatory)
Disable introspection in production. Configure depth limit, complexity limit and rate limiting for mutations. Verify field-level authorization in resolvers.
Performance (mandatory)
Identify N+1 problems with profiling. Use a batch loader for nested fields. CDN caching for unauthenticated data via persisted queries.
Testing (high)
Contract tests for all frontend queries in CI. GraphQL Inspector for schema diffs. Negative tests for error paths and auth scenarios.
Monitoring (high)
Operation name as a metric dimension. P95/P99 latency per query. Structured logging with complexity, depth and duration. Tracing for resolver paths.