Comprehensive Rules for building and maintaining scalable, secure, high-performance GraphQL services in enterprise environments.
Your team is building a GraphQL API that needs to scale across multiple services, handle enterprise security requirements, and maintain performance under load. You're tired of hunting through scattered documentation, inconsistent patterns, and debugging production issues that could have been prevented.
Building GraphQL APIs in enterprise environments isn't just about writing resolvers. You're dealing with:
The result? Your team spends more time fighting the toolchain than building features.
These Cursor Rules transform your GraphQL development by providing battle-tested patterns for every aspect of enterprise API development. From schema design to production monitoring, you get consistent, secure, performant code every time.
What you get:
// Before: Manual security checks scattered everywhere
const user = await getUserById(args.id); // No validation, no auth
// After: Security built into every resolver
export const user: QueryResolvers<Context>["user"] = async (_parent, { id }, ctx) => {
ctx.auth.assertLoggedIn(); // Automatic auth check
if (!isUUID(id)) throw ctx.errors.badUserInput("Invalid ID format");
return ctx.loaders.user.load(id);
};
// Automatic DataLoader batching prevents database overload
const users = await Promise.all(
postIds.map(id => ctx.loaders.user.load(id)) // Batched automatically
);
// Rich error context with correlation IDs and structured logging
{
"message": "User not found",
"extensions": {
"code": "USER_NOT_FOUND",
"timestamp": "2024-01-15T10:30:00Z",
"requestId": "req_123abc",
"userId": "user_456def"
}
}
Before: 30+ minutes of boilerplate setup
// Manual type definitions, resolver setup, validation logic
// Inconsistent error handling across team members
// Missing security checks discovered in code review
After: 5 minutes with consistent patterns
// Auto-generated types from schema
// Built-in validation and security
// Standardized error handling
export const createProduct: MutationResolvers<Context>["createProduct"] =
async (_parent, { input }, ctx) => {
ctx.auth.assertRole("ADMIN");
const product = await ctx.services.product.create(input);
return product;
};
Before: Hours of investigation with limited visibility
After: Immediate visibility with built-in observability
// Automatic tracing and metrics
// Query complexity limits prevent abuse
// DataLoader batching visible in logs
// OpenTelemetry integration shows full request flow
Before: Breaking changes and coordination nightmares
After: Controlled evolution with federation support
// Schema checks in CI/CD pipeline
// Automatic deprecation workflows
// Federation-ready entity definitions with @key directives
# Add to your .cursorrules file
curl -o .cursorrules https://cursor-rules.com/enterprise-graphql-rules
# Install required dependencies
npm install @apollo/server @apollo/federation graphql dataloader
npm install -D @types/node typescript graphql-codegen
src/
modules/
user/
schema.graphql # Domain-specific schema
resolvers.ts # Type-safe resolvers
dataloaders.ts # Batching logic
product/
...
lib/
errors.ts # Centralized error handling
auth.ts # Authentication logic
context.ts # Request context setup
// src/modules/user/schema.graphql
extend type Query {
user(id: ID!): User
}
type User @key(fields: "id") {
id: ID!
email: String!
profile: UserProfile
}
// src/modules/user/resolvers.ts
export const resolvers: Resolvers<Context> = {
Query: {
user: async (_parent, { id }, ctx) => {
ctx.auth.assertLoggedIn();
return ctx.loaders.user.load(id);
}
}
};
// Built-in query complexity limits
const server = new ApolloServer({
plugins: [
ApolloServerPluginUsageReporting(),
queryComplexityPlugin({ maximumComplexity: 1000 })
]
});
Ready to transform your GraphQL development? These rules have been battle-tested in production environments serving millions of requests. Your team will ship features faster, debug issues quicker, and sleep better knowing your APIs are secure and performant.
The difference between struggling with GraphQL complexity and mastering enterprise-grade APIs is having the right patterns from day one. Get them now.
You are an expert in GraphQL, TypeScript, Node.js, Apollo Server, Apollo Federation, GraphQL Modules, Relay, Apollo Client, DataLoader, PostgreSQL, and Docker.
Key Principles
- Client-driven design: model the schema around consumer use-cases, not DB tables.
- Versionless API: introduce non-breaking, additive changes; use @deprecated for field & enum retirement.
- Modular architecture: isolate domains into schema modules or sub-graphs; compose via Apollo Federation.
- Single source of truth: treat the schema as the contract; generate TypeScript types from it.
- Secure by default: assume every resolver needs auth & input validation.
- Fail fast, log always: validate early, capture context-rich errors, surface partial data when feasible.
- Observability first: include tracing, metrics, and structured logs from day one.
- Performant mindset: limit query depth/complexity, batch & cache I/O, paginate large lists.
TypeScript Rules
- Strict mode mandatory ("strict": true in tsconfig).
- Prefer interfaces over type aliases when describing shapes that may be implemented/extended.
- No implicit any; never use any in resolvers—use unknown + type-guards instead.
- Use exact return types for resolvers (e.g., Query['user']).
- Separate domain types (`src/@types/domain.ts`) from GraphQL-generated types (`src/@types/graphql.ts`).
- Use enums for finite sets (e.g., enum Role { ADMIN, USER }).
- Organize code as `src/modules/<domain>/{schema.ts,resolvers.ts,dataloaders.ts}`.
- Export default only once per file; name exports for everything else.
Error Handling & Validation
- Centralize error construction in `/src/lib/errors.ts`; expose createError({ code, message, extensions }).
- Standard GraphQL error shape:
{
"message": string,
"path": [string],
"extensions": { code: string, timestamp: string, requestId: string, ...custom }
}
- Use `graphql-constraint-directive` or custom directives (`@length`, `@pattern`) for input validation.
- Validate args early; return `UserInputError` if invalid.
- Resolver flow: validate → authZ check → data fetch → transform → return.
- Return partial data plus errors when feasible (e.g., batch fetch failure of one item).
- Map unknown errors to `ApolloError` with generic message, log original internally.
- Log errors with correlation IDs (requestId) and full stack traces using pino/Sentry.
Apollo Server / Apollo Federation Rules
- Federation sub-graph layout:
- `src/modules/<domain>/schema.graphql`
- `src/modules/<domain>/resolvers.ts`
- `src/index.ts` (gateway entry or sub-graph start)
- Gateway must enable `serviceList` polling & managed schema checks.
- Use `@key(fields:"id")` on every entity that crosses sub-graphs.
- Put authentication in `context` function; attach user & permissions to context for resolvers.
- Fine-grained authorization:
- Declarative `@auth(role:"ADMIN")` directive where possible.
- Fallback to imperative checks inside resolvers.
- Disable introspection & playground in production (`introspection: false` and env-guarded landing page).
- Use `ApolloServerPluginUsageReporting` & `ApolloServerPluginInlineTrace` for metrics.
- Implement `formatError` & `formatResponse` for uniform envelopes.
Performance Rules
- Enforce query depth <= 10 & cost <= 1000 using `graphql-validation-complexity`.
- Wrap DB calls with DataLoader; batch by primary key.
- Short-circuit unauthenticated requests before DB call.
- Use `@cacheControl` hints (maxAge, scope) in resolvers.
- Prefer connection-style pagination (Relay spec) for lists > 100 items.
- Only select requested columns in SQL (use GraphQL-to-SQL projection library when possible).
- Warm cache on write (write-through) for high-traffic nodes.
Testing Rules
- Unit test resolvers with mocked context (`jest.mockedContext`).
- Snapshot test the full schema SDL (`toMatchSnapshot`).
- Integration tests: spin Docker-compose (service + DB) and hit real HTTP endpoint.
- Use `graphql-tag` + `apollo-server-testing` to run queries.
- Each PR must add/adjust tests for schema changes.
Security Rules
- Sanitize all string inputs via `DOMPurify` equivalent on server (`sanitize-html`).
- Rate-limit per IP & authId using `express-rate-limit` in gateway.
- Reject queries containing `__schema` or `__type` fields when introspection disabled.
- Encrypt secrets in env vars (AWS KMS/ GCP Secret Manager); never commit `.env`.
- Use HTTPS everywhere; enforce HSTS.
- Regularly audit dependencies (`npm audit --production`).
Observability & Monitoring
- Inject `requestId` middleware; propagate via context & logs.
- Use OpenTelemetry instrumentation (`@opentelemetry/instrumentation-graphql`).
- Export traces to Honeycomb or Jaeger.
- Surface key metrics: request_count, p95_latency, error_rate, cache_hit_rate.
- Alert on depth > threshold & sustained error spikes.
Documentation Rules
- Add `description` to every type, field, and argument in SDL.
- Auto-publish schema to Apollo Studio on merge to main.
- Maintain a living changelog (`docs/CHANGELOG.md`) using Conventional Commits.
CI/CD Rules
- Lint (`eslint`, `graphql-eslint`) and type-check on every push.
- Reject PR if schema validation fails or coverage < 90%.
- Auto-deploy to staging on PR merge; prod on tagged release.
- Run `rover subgraph check` & `rover subgraph publish` gates.
Directory Skeleton (reference)
src/
modules/
user/
schema.graphql
resolvers.ts
dataloaders.ts
product/
...
lib/
errors.ts
auth.ts
context.ts
index.ts
server.ts
test/
unit/
integration/
docs/
CHANGELOG.md
architecture.md
Example Resolver (concise, typed)
``ts
import { QueryResolvers, User } from "@types/graphql";
import { Context } from "../lib/context";
export const user: QueryResolvers<Context>["user"] = async (_parent, { id }, ctx): Promise<User | null> => {
ctx.auth.assertLoggedIn();
if (!isUUID(id)) throw ctx.errors.badUserInput("Invalid ID format");
const user = await ctx.loaders.user.load(id); // DataLoader batching
return user ?? null;
};
```
Adopt these rules to enforce consistency, safety, and performance across all enterprise GraphQL services.