# GRAPHQL

> Use when the project uses GraphQL for API queries and mutations

- Skill: `tomevault-io/graphql-6` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/graphql-6`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/graphql-6/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/graphql-6

---


# GraphQL Best Practices

## Schema Design

Design your schema around the client's needs, not your database tables. Use descriptive type names and field names. Every field should have a clear purpose -- do not expose internal implementation details.

Use `ID` type for identifiers, `String` for text, `Int`/`Float` for numbers, `Boolean` for flags. Define custom scalars (DateTime, URL, Email) for domain-specific values that need validation.

Make fields non-nullable by default (`String!`). Only use nullable fields when null carries meaning (e.g., "no value set"). Lists should be non-nullable with non-nullable items: `[User!]!`.

## Queries and Mutations

Separate reads (queries) from writes (mutations). Mutations should return the affected object so clients can update their cache without a refetch. Name mutations as verbs: `createUser`, `updatePost`, `deleteComment`.

Use input types for mutation arguments: `input CreateUserInput { name: String!, email: String! }`. This keeps the schema clean and makes validation straightforward.

## Pagination

Use Relay-style cursor pagination for all list fields: `Connection` type with `edges`, `node`, `pageInfo`, and `cursor`. This handles infinite scroll, bidirectional loading, and real-time updates cleanly. Avoid offset-based pagination -- it breaks when data changes between pages.

## N+1 Problem

GraphQL's nested resolution model naturally creates N+1 queries. Use DataLoader to batch and cache database lookups within a single request. Every resolver that touches the database should go through a DataLoader.

Without DataLoader, a query fetching 50 users with their posts makes 51 database calls (1 for users + 50 for each user's posts). With DataLoader, it makes 2.

## Error Handling

Return errors in the `errors` array, not in the data. Use error `extensions` for machine-readable codes: `{ "extensions": { "code": "UNAUTHORIZED" } }`. Partial failures are valid in GraphQL -- a query can return data for some fields and errors for others.

## Security

Limit query depth (typically 7-10 levels) to prevent deeply nested queries that overwhelm the server. Limit query complexity by assigning cost values to fields and rejecting queries that exceed a budget.

Disable introspection in production unless your API is intentionally public. Introspection exposes your entire schema to attackers.

## Performance

Persisted queries reduce bandwidth and prevent arbitrary query execution. Clients send a hash instead of the full query string. Cache responses at the field level, not the query level, since GraphQL queries are highly variable.

---
> Source: [calcosmic/Aether](https://github.com/calcosmic/Aether) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-15 -->

