--- description: This rule provides comprehensive best practices and coding standards for developing with Apollo GraphQL. It covers schema design, security, performance, testing, and deployment. globs: *.js,*.jsx,*.ts,*.tsx,*.graphql --- # Apollo GraphQL Best Practices This document provides a comprehensive guide to best practices and coding standards for developing GraphQL APIs and applications using the Apollo ecosystem. ## Library Information: - Name: apollo-graphql - Tags: web, api, graphql, javascript ## 1. Schema Design - **Clarity and Consistency:** - Use descriptive naming conventions for types, fields, and arguments (e.g., `getUserById` instead of `getUser`). - Maintain consistency across the schema in terms of naming and structure. - Define a clear schema that reflects your business domain and data model. - **Interfaces and Unions:** - Implement interfaces and unions for shared features and common data structures. - Use interfaces to define contracts for types that implement shared behaviors. - Use unions when a field can return different object types that don't share a common interface. - **Nullability:** - Every field is nullable by default unless explicitly marked as non-null using `!`. Carefully consider nullability for each field. - Use non-null types (`!`) only when you can guarantee that the field will always return a value. - Handle potential errors gracefully and return `null` for nullable fields when appropriate. - **Demand-Driven Schema Design:** - Design your schema to serve client use cases and product requirements. - Intentionally design your schema to serve client use cases and product requirements. - **Avoid Autogenerated Schemas:** - Avoid autogenerating schemas, especially the fields on the root operation types ## 2. Security - **Disable Introspection in Production:** - Disable introspection in production environments to prevent unauthorized access to your schema. - Restrict access to staging environments where introspection is enabled. - **Input Validation:** - Validate all user inputs to prevent injection attacks and data corruption. - Use custom scalars or directives to enforce validation rules on input types. - Sanitize and escape user inputs before using them in resolvers. - **Authentication and Authorization:** - Implement robust authentication and authorization mechanisms to protect your API. - Use industry-standard protocols like OAuth 2.0 or JWT for authentication. - Implement fine-grained authorization checks at the field level. - Enforcing authentication and authorization in the router protects your underlying APIs from malicious operations - **Rate Limiting and Depth Limiting:** - Implement rate limiting to prevent abuse and denial-of-service attacks. - Limit query depth to prevent complex queries from overwhelming your server. - **Whitelisting Queries:** - Consider whitelisting queries to restrict the operations that can be executed against your API. - **Obfuscate Error Details:** - Remove verbose error details from API responses in your production graph. - Only selectively expose error details to clients in production. - **Data Validation and Sanitization:** - As a schema design best practice, you should deliberately design your schema to serve client use cases and product requirements. ## 3. Performance Optimization - **Batching and Caching:** - Utilize batching techniques (e.g., DataLoader) to reduce the number of requests to backend data sources. - Implement caching at different levels (e.g., server-side, client-side) to improve response times. - **Pagination:** - Implement pagination strategies to manage large datasets effectively. - Use cursor-based pagination for efficient retrieval of paginated data. - **N+1 Problem:** - Use tools like DataLoader to address the N+1 query problem and ensure efficient data fetching. - **Server-Side Batching & Caching:** - GraphQL is designed in a way that allows you to write clean code on the server, where every field on every type has a focused single-purpose function for resolving that value. - **Automatic Persisted Queries (APQ):** - Consider implementing automatic persisted queries (APQ) to optimize network usage and improve security ## 4. Code Organization and Structure - **Directory Structure:** src/ schema/ types/ *.graphql resolvers/ *.js dataSources/ *.js utils/ *.js index.js // Entry point - **File Naming Conventions:** - Use PascalCase for type definitions (e.g., `UserType.graphql`). - Use camelCase for resolver functions (e.g., `getUserById.js`). - **Module Organization:** - Organize your code into reusable modules based on functionality (e.g., user management, product catalog). - Create separate modules for schema definitions, resolvers, and data sources. - **Component Architecture:** - Follow a component-based architecture for building GraphQL applications. - Create reusable components for common UI elements and data fetching logic. - **Code Splitting:** - Use code splitting to reduce the initial bundle size and improve page load times. - Consider splitting your application into smaller chunks based on routes or features. ## 5. Common Patterns and Anti-patterns - **Design Patterns:** - **Data Source Pattern:** Decouple data fetching logic from resolvers using data sources. - **Schema Stitching:** Combine multiple GraphQL APIs into a single, unified schema. - **Recommended Approaches:** - Use a GraphQL client library (e.g., Apollo Client, Relay) for efficient data fetching and caching. - Implement custom directives to add additional functionality to your schema. - **Anti-patterns:** - **Over-fetching/Under-fetching:** Avoid returning more or less data than required by the client. - **Chatty APIs:** Reduce the number of round trips between the client and the server. - **God Objects:** Avoid creating large, monolithic types with too many fields. - **State Management:** - Use a state management library (e.g., Redux, Zustand, Jotai) to manage client-side state. - Consider using Apollo Client's local state management features for simple state requirements. - **Error Handling:** - Use a consistent error handling mechanism across your application. - Return informative error messages to the client. - Log errors on the server for debugging purposes. ## 6. Testing Approaches - **Unit Testing:** - Unit test individual resolvers and data sources. - Mock external dependencies to isolate the code under test. - **Integration Testing:** - Integrate test your GraphQL API with your database and other backend services. - Use a testing framework like Jest or Mocha for writing integration tests. - **End-to-End Testing:** - Use end-to-end testing to verify the entire application flow. - Use a testing tool like Cypress or Puppeteer for writing end-to-end tests. - **Test Organization:** - Organize your tests into separate directories based on functionality. - Use descriptive names for your test files and test cases. - **Mocking and Stubbing:** - Use mocking and stubbing techniques to isolate the code under test and simulate external dependencies. ## 7. Common Pitfalls and Gotchas - **N+1 Problem:** Be aware of the N+1 query problem and use DataLoader or other batching techniques to solve it. - **Schema Evolution:** Plan for schema evolution and use techniques like adding new fields and deprecating old ones to avoid breaking changes. - **Performance Bottlenecks:** Monitor your API for performance bottlenecks and use profiling tools to identify slow resolvers. - **Nullability:** Ensure that non-null fields never return `null` to avoid unexpected errors. ## 8. Tooling and Environment - **Development Tools:** - Use a GraphQL IDE like GraphiQL or Apollo Studio for exploring and testing your API. - Use code generation tools to generate types and resolvers from your schema. - **Build Configuration:** - Use a build tool like Webpack or Parcel to bundle your code for production. - Configure your build tool to optimize your code and reduce bundle size. - **Linting and Formatting:** - Use a linter like ESLint or Prettier to enforce code style and prevent errors. - **Deployment:** - Deploy your GraphQL API to a production environment like AWS Lambda, Google Cloud Functions, or a Node.js server. - Use a serverless platform for easy scaling and management. - **CI/CD Integration:** - Integrate your GraphQL API with a CI/CD pipeline for automated testing and deployment. ## 9. Additional Best Practices - **Versioning:** While GraphQL promotes continuous evolution, consider versioning your API if you need to make breaking changes. - **Documentation:** Provide comprehensive documentation for your GraphQL API using tools like GraphQL Docs. - **Monitoring:** Monitor your GraphQL API for performance and errors using tools like Apollo Studio or New Relic. - **Error Messages and Notifications:** You can also opt for union types to represent an error and to prompt suggestions to users, though this is a more expensive choice. ## 10. Global Identification - Another way to organize components, besides Pagination, is by using a global identification. Originally proposed on Relay and similar to URIs, this method has become a more general good practice though it is not considered mandatory — especially if you are not planning on supporting Relay in your application ## 11. Rate Limiting - Like any other web API, setting limits is a good strategy to avoid, for example, an overload of requests per minute. There are a few ways this can be done in GraphQL ## 12. Authentication and Authorization - Often interchanged in their meaning, authentication is the act of determining who a user is and whether they are logged in or not. Authorization, on the other hand, is the act of determining if a user is allowed to do an action or see something. ## 13. Safelisting with Persisted queries - Beyond operation limits, GraphOS enables first-party apps to register trusted operations in a persisted query list ( PQL) or safelist.