GraphQL API
Best Practices
GraphQL best practices and patterns
Best Practices
Use Named Queries
Always name your queries for better debugging and monitoring:
# Good
query GetProductList {
products { ... }
}
# Bad
query {
products { ... }
}Use Fragments for Reusability
Define reusable fragments for common field selections:
fragment ProductBasic on Product {
id
title
handle
thumbnail
status
}
query GetProducts {
products(limit: 10) {
products {
...ProductBasic
}
}
}
query GetProduct($id: String!) {
product(id: $id) {
...ProductBasic
description
variants {
id
title
}
}
}Use Variables for Dynamic Values
Never interpolate values directly into queries:
// Good
const query = gql`
query GetProduct($id: String!) {
product(id: $id) { ... }
}
`;
client.query({ query, variables: { id: productId } });
// Bad - Security risk
const query = gql`
query {
product(id: "${productId}") { ... }
}
`;Handle Loading and Error States
Always handle loading and error states in your UI:
function ProductList() {
const { loading, error, data } = useQuery(GET_PRODUCTS);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <ProductGrid products={data.products.products} />;
}Implement Retry Logic
Implement exponential backoff for rate limit errors:
async function queryWithRetry(query, variables, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await executeQuery(query, variables);
} catch (error) {
if (error.extensions?.code === 'RATE_LIMIT_EXCEEDED') {
const retryAfter = error.extensions.retryAfter || Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}Monitor Query Performance
Log slow queries for optimization:
const startTime = Date.now();
const result = await executeQuery(query, variables);
const duration = Date.now() - startTime;
if (duration > 1000) {
console.warn(`Slow query (${duration}ms):`, query);
}