GraphQL best practices
GraphQL is mature enough, and has been around us for long enough, that many articles sharing best practices have been published by the community. These guides involve pretty much all aspects of GraphQL, including schema design, naming conventions, security handling and providing meaningful errors, among others.
These are some of the most compelling guides out there on best practices in GraphQL.
Best practices on graphql.org
The official GraphQL site offers a general introduction to best practices in GraphQL.
These items cover mostly top-level concerns, such as:
- What's the best way to paginate the results
- Where the GraphQL layer stands within the architecture
- How to use the Node interface for global object identification
- How to cache results
- Many others
Lee Byron's recommendations
Not long after releasing GraphQL to the world, GraphQL creator Lee Byron published article Lessons From 4 Years of GraphQL, describing how we should conceptually attempt to work with GraphQL:
- Naming matters
- Think in graphs, not endpoints
- Describe the data, not the view
- GraphQL is a thin interface
- Hide implementation details
He also details several valuable principles and lessons he learnt while using GraphQL in Facebook.
GraphQL Rules
GraphQL Rules is a site specially devoted to presenting every-day best practices for working with GraphQL, mostly concerning the design of the GraphQL schema.
This resource is very thorough. It compiles the information from a few exceptional resources out there (such as article Designing GraphQL Mutations and Shopify's tutorial Designing a GraphQL API) and presents them all together in a concise manner.
The described rules include:
- Naming rules
- Use
camelCase
for GraphQL-fields and arguments. - Use
UpperCamelCase
for GraphQL-types. - Use
CAPITALIZED_WITH_UNDERSCORES
to name ENUM-types.
- Use
- Type rules
- Use custom scalar types if you want to declare fields or args with specific semantic value.
- Use Enum for fields which contain a specific set of values.
- Field Rules (Output)
- Use semantic names for fields and avoid leaking of implementation details in fields names.
- Use
Non-Null
field if field will always have a given field value. - Group as many related fields into custom Object type as possible.
- Argument rules (Input)
- Group coupled arguments to the new input-type.
- Use strict scalar types for arguments, eg.
DateTime
instead ofString
. - Mark arguments as
required
, is they are required for query execution.
- Rules of lists
- To filter the lists, use the
filter
argument, which contains all the available filters. - Use argument
sort
of typeEnum
or[Enum!]
for listings sorting. - Use
limit
with default value andskip
to limit number of returned items in list. - Use
page
,perPage
args for pagination and return output type withitems
(array of elements) andpageInfo
(meta-data). - For infinite lists (infinite scroll) use Relay Cursor Connections Specification.
- To filter the lists, use the
- Mutation rules
- Use Namespace-types to group mutations within a single resource.
- Go beyond CRUD – create small mutations for different business operations on resources.
- Consider the ability to perform mutations on multiple items (same type batch changes).
- Mutations should clearly describe all the mandatory arguments, there should be no options either-either.
- In mutations, put all variables into one unique input argument.
- Every mutation should have a unique payload type.
Resolvers best practices
Article GraphQL Resolvers: Best Practices describes how to best create field resolvers. Even though it is aimed at Node.js servers (PayPal's infrastructure is based on Express), several of his lessons can also be applied for other technologies, including PHP.
The main takeaways are:
- Fetching and passing data from parent-to-child should be used sparingly.
- Use libraries like dataloader to de-dupe downstream requests.
- Be aware of any pressure you’re causing on your data sources.
- Don’t mutate “context”. Ensures consistent, less buggy code.
- Write resolvers that are readable, maintainable, testable. Not too clever.
- Make your resolvers as thin as possible. Extract out data fetching logic to re-usable async functions.
OWASP - GraphQL Cheat Sheet
OWASP (Open Web Application Security Project) is a nonprofit foundation that works to improve the security of software. It does research on how different technologies are vulnerable to exploits, and thoroughly describes solutions to the security issues, making it easier for developers to prevent attacks.
OWASP has published the GraphQL Cheat Sheet, explaining which are the most common attacks and biggest security issues in GraphQL, and how to address them.
Common attacks to GraphQL are:
- Injection - this usually includes but is not limited to:
- SQL and NoSQL injection
- OS Command injection
- SSRF and CRLF injection/Request Smuggling
- DoS (Denial of Service)
- Abuse of broken authorization: either improper or excessive access, including IDOR
- Batching Attacks, a GraphQL-specific method of brute force attack
- Abuse of insecure default configurations
OWASP then provides recommendations on how to avoid each of these.
Best practices with GraphQL queries
The Apollo team published the GraphQL query best practices, giving practical insights on practical ways to compose the GraphQL query.
For instance, these two queries achieve the same goal, but because the first one has an operation name, it is more understandable and helpful when debugging:
Their suggestions include:
- Name all operations
- Use variables to provide GraphQL arguments
- Query only the data you need, where you need it
- Use fragments to encapsulate related sets of fields
- Query global data and user-specific data separately
Leveraging the one graph
Also from the Apollo team is site Principled GraphQL which explains that GraphQL is not only a specification but, possibly more importantly, an interface to interact with the data "graph" from our company.
Through a list of 10 principles, this site describes how to make the most out of the single graph:
- One Graph: Your company should have one unified graph, instead of multiple graphs created by each team.
- Federated Implementation: Though there is only one graph, the implementation of that graph should be federated across multiple teams.
- Track the Schema in a Registry: There should be a single source of truth for registering and tracking the graph.
- Abstract, Demand-Oriented Schema: The schema should act as an abstraction layer that provides flexibility to consumers while hiding service implementation details.
- Use an Agile Approach to Schema Development: The schema should be built incrementally based on actual requirements and evolve smoothly over time.
- Iteratively Improve Performance: Performance management should be a continuous, data-driven process, adapting smoothly to changing query loads and service implementations.
- Use Graph Metadata to Empower Developers: Developers should be equipped with rich awareness of the graph throughout the entire development process.
- Access and Demand Control: Grant access to the graph on a per-client basis, and manage what and how clients can access it.
- Structured Logging: Capture structured logs of all graph operations and leverage them as the primary tool for understanding graph usage.
- Separate the GraphQL Layer from the Service Layer: Adopt a layered architecture with graph functionality broken into a separate tier rather than baked into every service.