- Use `DataLoader` for every resolver that fetches a related entity — it batches and caches DB calls within a single request to eliminate N+1 queries.
- Enable `@cacheControl` directives on types and fields to let Apollo Server set correct HTTP cache headers for CDN caching of public data.
- Limit query complexity and depth with `graphql-query-complexity` and `graphql-depth-limit` to prevent abusive queries from exhausting server resources.
- Create one `DataLoader` instance per request (in context) — never share DataLoader instances across requests as the per-request cache would serve stale data.
- Use `dataloader-cache-map` or a Redis-backed cache for cross-request batching of expensive, slowly-changing data like permission lookups.
- Enable query result caching with `@apollo/server-cache-redis` for queries that depend only on public, cacheable data.
- Use `graphql-query-complexity` with cost-based scoring: simple scalars cost 1, list fields cost `(child cost) * multiplier` — reject queries above a threshold.