<!-- skill: GraphQL API | version: 1.0.0 | description: Design schema-first GraphQL APIs with resolvers, auth and dataloaders | category: Web -->
## Instructions
- Schema-first SDL: write `schema.graphql` with `type Query/Mutation/Subscription`, input types `CreatePostInput`, enums, `scalar DateTime`; server code follows the schema, not vice versa.
- Naming: PascalCase types, camelCase fields, `ID!` for identifiers, nullable only when genuinely optional; paginate with Relay `Connection { edges, pageInfo }` or `limit/offset` documented.
- Resolvers: one function per field, thin — delegate to service/store layer; never N+1 — batch with dataloader per request (`userLoader.load(id)`), prime cache after writes.
- Queries/mutations: queries are pure reads with args validation; mutations return payload types (`CreatePostPayload { post, errors }`), take single `input:` arg, are idempotent-safe via `clientMutationId`.
- Subscriptions: websocket transport (`graphql-ws`), topic per entity (`postAdded`), auth ticket at connection init; keep payloads small, fall back to polling for caches.
- Auth: field/middleware level — `@auth(requires: ADMIN)` directive or context `user`; validate JWT in context builder, deny-by-default, log authz failures without leaking existence.
- Errors: typed `extensions.code` (`UNAUTHENTICATED/FORBIDDEN/NOT_FOUND/BAD_USER_INPUT`), `UserError { message, field }` for form errors; never stack traces to clients.
- Validation: max query depth (8-12) + complexity limits, persisted queries in prod, `MaxBytesReader`/body limit, introspection OFF in production.
- Tooling: Apollo Server v4 / Yoga / gqlgen (Go) / Strawberry (Python); codegen (`graphql-codegen`) for typed clients; GraphiQL/Sandbox only in dev, gated in prod.
- Testing: unit resolvers with mocked store, full `execute(query)` integration tests, snapshot SDL (`rover`/`graphql-inspector diff`) to catch breaking changes in CI.
- Caching: `@cacheControl(maxAge: 60)` hints, CDN cache only for public queries (key on query hash + vars), per-request dataloader cache cleared after each request.
- Versioning: evolve additively — deprecate (`@deprecated(reason:)`) instead of removing; breaking change needs new field + migration window + client codemod note.
## Examples
- Blog API: `Post/Author` types, `posts(first:10)` connection, `createPost(input:)` mutation, author dataloader killing N+1, Apollo Sandbox dev tour.
- Auth-gated mutations: JWT context, `@auth` on `deletePost`, `UserError` payloads for validation, depth-limit + introspection-off prod config.
- Realtime comments: `commentAdded(postId:)` subscription over `graphql-ws`, auth on connect, dataloader-primed publish path.
## Workflows
1. Draft SDL schema with types, inputs, queries, mutations, subscriptions.
2. Implement stores/services, then resolvers + per-request dataloaders.
3. Add auth context + directives, error codes, depth/complexity limits.
4. Codegen typed clients + write resolver/integration tests + SDL diff check.
5. Expose GraphiQL/Sandbox in dev; lock down introspection + persisted queries prod.
6. Monitor resolver latency per field, cache hints, subscription fan-out load.
## Templates
- Schema-first SDL:
```graphql
scalar DateTime
type User { id: ID! name: String! email: String! }
type Post { id: ID! title: String! body: String! author: User! createdAt: DateTime! }
type PostEdge { cursor: String! node: Post! }
type PostConnection { edges: [PostEdge!]! pageInfo: PageInfo! }
type PageInfo { hasNextPage: Boolean! endCursor: String }
type UserError { message: String! field: String }
input CreatePostInput { title: String! body: String! clientMutationId: String }
type CreatePostPayload { post: Post userErrors: [UserError!]! }
type Query { posts(first: Int = 10, after: String): PostConnection! post(id: ID!): Post }
type Mutation { createPost(input: CreatePostInput!): CreatePostPayload! }
type Subscription { postAdded: Post! }
```
- Apollo Server (Node) with auth + dataloader:
```js
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import DataLoader from 'dataloader';
import { readFileSync } from 'fs';
const typeDefs = readFileSync('./schema.graphql', 'utf8');
const resolvers = {
  Query: {
    post: (_, { id }, { stores }) => stores.posts.get(id),
    posts: (_, { first, after }, { stores }) => stores.posts.page(first, after),
  },
  Post: {
    author: (post, _, { loaders }) => loaders.user.load(post.authorId),
  },
  Mutation: {
    createPost: async (_, { input }, { user, stores }) => {
      if (!user) throw new GraphQLError('auth required',
        { extensions: { code: 'UNAUTHENTICATED' } });
      if (input.title.length < 3)
        return { post: null, userErrors: [{ message: 'too short', field: 'title' }] };
      const post = await stores.posts.create({ ...input, authorId: user.id });
      return { post, userErrors: [] };
    },
  },
};
const server = new ApolloServer({ typeDefs, resolvers,
  validationRules: [depthLimit(10)], introspection: process.env.NODE_ENV !== 'production' });
await server.start();
app.use('/graphql', expressMiddleware(server, { context: async ({ req }) => ({
  user: await authUser(req.headers.authorization),
  loaders: { user: new DataLoader((ids) => batchUsers(ids)) },
})}));
```
- Depth-limit + codegen config:
```js
// codegen.ts
export default {
  schema: './schema.graphql',
  documents: 'src/**/*.graphql',
  generates: { 'src/gql/': { preset: 'client' } },
};
```
## Tools
- SARAH shell_run for `graphql-codegen`, `rover graph check`, `graphql-inspector diff`, server boot + introspection curl.
- SARAH file_write for SDL files, resolver scaffolds, codegen configs; Apollo Sandbox / GraphiQL for manual runs.
## Advanced
- Federation: Apollo Federation `buildSubgraphSchema` + gateway composition, `@key(fields:"id")` entities, contact per subgraph; schema registry checks block breaking merges.
- Persisted ops: `PersistedQueryLink` + APQ hashing, allow-list in prod, CDN edge-cache GET queries by sha256; complexity scoring per field (`cost: 10` on lists).
- Subscriptions scale: Redis pub/sub backplane, `graphql-ws` keepalive + connectionParams auth, fallback SSE for restricted networks.
## Documentation
- https://graphql.org/learn/
- https://www.apollographql.com/docs/apollo-server/
- https://the-guild.dev/graphql/yoga-server
- https://github.com/graphql/dataloader
- https://graphql-code-generator.com/
