Comparisons

REST vs GraphQL: AI Rules Comparison

REST and GraphQL are fundamentally different API paradigms. Each needs different AI rules for endpoint design, data fetching, error handling, caching, and type safety. Side-by-side rules with templates for both.

8 min read·April 30, 2025

HTTP 404 in a GraphQL resolver breaks the contract — same API goal, incompatible paradigms

Resources vs schema, over-fetching vs precise queries, HTTP status vs errors array, and rule templates

Different Paradigms, Different Everything

REST and GraphQL solve the same problem (client-server data exchange) with fundamentally different approaches. REST: multiple endpoints, each returning a fixed data shape. GET /api/users/123 returns the full user object. GET /api/users/123/posts returns the user posts. The server decides what data to return. GraphQL: single endpoint (/graphql), the client specifies exactly which fields it needs. query { user(id: "123") { name, email, posts { title } } }. The client decides what data to return.

Without API paradigm rules: AI generates REST endpoints in a GraphQL project (creating /api/users when the project uses a GraphQL schema), writes GraphQL resolvers in a REST project (resolver functions that do not match the Express/Fastify route pattern), uses HTTP status codes for GraphQL errors (GraphQL always returns 200, errors are in the response body), or tries to cache GraphQL responses with HTTP caching (GraphQL POST requests are not HTTP-cacheable by default). The paradigm shapes every API decision.

This article provides: the AI rules for each paradigm, the key design differences, and copy-paste CLAUDE.md templates. The rules tell the AI: this is a REST API (use HTTP methods, status codes, resource URLs) or this is a GraphQL API (use schema, resolvers, queries, mutations) — preventing every paradigm-crossing error.

Endpoint Design: Resources vs Schema

REST endpoint design: resources as nouns, HTTP methods as verbs. GET /api/users (list), GET /api/users/:id (detail), POST /api/users (create), PUT /api/users/:id (update), DELETE /api/users/:id (delete). Each resource has its own URL. Nested resources: GET /api/users/:id/posts. The URL structure IS the API design. AI rule: 'REST: resource-based URLs, HTTP methods for CRUD. GET for reads, POST for creates, PUT/PATCH for updates, DELETE for deletes. Each resource: its own endpoint path.'

GraphQL schema design: types define the data shape, queries read, mutations write. type User { id: ID!, name: String!, email: String!, posts: [Post!]! }. type Query { user(id: ID!): User, users: [User!]! }. type Mutation { createUser(input: CreateUserInput!): User! }. One endpoint (/graphql) handles all operations. The schema IS the API design — no URLs to design, no HTTP methods to choose. AI rule: 'GraphQL: define types in the schema. Queries for reads, Mutations for writes. One /graphql endpoint. Resolvers implement the schema.'

The design rule prevents: AI creating GET /api/users routes in a GraphQL project (use Query type instead), defining GraphQL type schemas in a REST project (REST uses JSON response shapes, not GraphQL SDL), or mixing both paradigms in one API (confusing for clients and developers). The paradigm determines how the AI structures every data access pattern.

  • REST: nouns as URLs (GET /api/users/:id), HTTP methods as verbs (GET/POST/PUT/DELETE)
  • GraphQL: types as schema, Query for reads, Mutation for writes, one /graphql endpoint
  • REST: URL structure IS the API. GraphQL: schema IS the API
  • REST: multiple endpoints per resource. GraphQL: one endpoint, client specifies data needs
  • AI error: REST routes in GraphQL project = unused code. GraphQL types in REST = wrong paradigm
💡 URL Structure IS the API vs Schema IS the API

REST: the URL design (/api/users/:id/posts) defines the API. Each resource has its own path. GraphQL: the schema (type User { posts: [Post] }) defines the API. One URL (/graphql) handles everything. The paradigm determines how the AI structures every data access point.

Data Fetching: Over/Under-Fetching vs Precise Queries

REST data fetching challenge: over-fetching (GET /api/users/123 returns 20 fields when the client needs 3) and under-fetching (need user + posts + comments = 3 API calls). Solutions: field selection (?fields=name,email), includes (?include=posts), and purpose-built endpoints (/api/dashboard aggregating multiple resources). These are REST workarounds for a fixed-response-shape limitation. AI rule: 'REST: design endpoints for common use cases. Support ?fields for sparse responses. Create aggregation endpoints for dashboard-style pages. Accept over-fetching as a trade-off for simplicity.'

GraphQL data fetching: precise by design. query { user(id: "123") { name, email, posts { title, createdAt } } } returns exactly: name, email, and post titles with dates. No over-fetching (only requested fields). No under-fetching (user + posts in one query). No aggregation endpoints needed (the query IS the aggregation). The client controls the response shape. AI rule: 'GraphQL: clients specify exactly the fields they need. No over-fetching or under-fetching. Design the schema for flexibility — clients compose queries for their specific needs.'

The fetching rule changes how AI designs data access: REST rules guide toward well-designed endpoints with optional field selection. GraphQL rules guide toward a comprehensive schema that clients query flexibly. AI generating REST-style fixed responses in a GraphQL resolver (returning all fields regardless of the query) defeats the purpose of GraphQL. AI generating GraphQL-style flexible queries in REST (complex query parameters) over-engineers the REST endpoint.

Error Handling: HTTP Status vs Errors Array

REST error handling: HTTP status codes carry the error type. 400 Bad Request (validation error), 401 Unauthorized (not authenticated), 403 Forbidden (not authorized), 404 Not Found (resource does not exist), 409 Conflict (state conflict), 500 Internal Server Error (unexpected failure). The status code is the primary error indicator. The body provides details: { error: { code: "VALIDATION_ERROR", message: "Email is invalid" } }. AI rule: 'REST: use HTTP status codes for error types. 4xx for client errors, 5xx for server errors. Response body for error details.'

GraphQL error handling: the HTTP status is always 200 (even on errors). Errors are in the response body: { data: null, errors: [{ message: "User not found", extensions: { code: "NOT_FOUND" }, path: ["user"] }] }. Partial success is possible: { data: { user: { name: "Alice" }, posts: null }, errors: [{ message: "Posts service unavailable", path: ["user", "posts"] }] }. The errors array can contain multiple errors from different resolvers. AI rule: 'GraphQL: always return 200. Errors in the errors array with message, extensions.code, and path. Support partial success — some fields resolve, others error.'

The error rule is critical: AI returning HTTP 404 in a GraphQL resolver breaks the GraphQL contract (clients expect 200 with errors in the body). AI returning errors in the response body for REST (without the appropriate HTTP status code) confuses REST clients that check status codes first. The error mechanism is paradigm-determined. One rule prevents every error handling mismatch.

  • REST: HTTP status codes (400, 401, 403, 404, 500) + error details in body
  • GraphQL: always 200, errors in response body errors array with path and extensions
  • GraphQL supports partial success: some fields resolve, others return errors
  • REST: one error per response. GraphQL: multiple errors possible (one per failing resolver)
  • AI error: HTTP 404 in GraphQL = breaks contract. No status code in REST = confusing clients
⚠️ Always 200, Errors in Body

REST: HTTP 404 means 'not found.' GraphQL: HTTP is always 200. 'Not found' is in the response body: errors: [{ message: 'User not found', extensions: { code: 'NOT_FOUND' } }]. AI returning HTTP 404 in a GraphQL resolver breaks every GraphQL client that expects 200.

Caching: HTTP Cache vs Normalized Cache

REST caching: built on HTTP caching. Cache-Control headers on GET responses. CDNs cache responses by URL. ETag and If-None-Match for conditional requests. The HTTP caching infrastructure (browsers, CDNs, proxies) works automatically with REST because each resource has a unique URL. Cache invalidation: by URL (purge /api/users/123 when user 123 changes). AI rule: 'REST: use Cache-Control headers. CDN caching by URL. ETag for conditional GET. HTTP caching infrastructure works automatically.'

GraphQL caching: HTTP caching does not work well (all requests go to the same URL via POST — CDNs cannot cache POST requests by default). Client-side: normalized cache (Apollo Client cache normalizes objects by ID + __typename, updates across all queries when one query refreshes an object). Server-side: persisted queries (predefine queries, cache by query hash), or CDN caching with @cacheControl directives. AI rule: 'GraphQL: use Apollo Client normalized cache. Server: persisted queries for CDN caching. @cacheControl directive for field-level TTL. HTTP caching does not apply to standard GraphQL POST.'

The caching rule prevents: AI adding Cache-Control headers to GraphQL POST responses (CDNs do not cache POST), relying on URL-based cache invalidation for GraphQL (one URL for all queries), or using Apollo cache patterns for REST APIs (REST does not use normalized caching). The caching mechanism is paradigm-determined: REST leverages HTTP infrastructure. GraphQL requires application-level caching solutions.

ℹ️ HTTP Cache Works for REST, Not GraphQL

REST: Cache-Control headers + CDN = automatic HTTP caching by URL. GraphQL: all requests POST to /graphql — CDNs do not cache POST. GraphQL needs: Apollo normalized cache (client), persisted queries (CDN), or @cacheControl directives. Different infrastructure for the same goal.

Ready-to-Use Rule Templates

REST CLAUDE.md template: '# API (REST). Design: resource-based URLs with HTTP methods. Endpoints: GET (read), POST (create), PUT/PATCH (update), DELETE (remove). Response: JSON with appropriate HTTP status codes. Errors: status code + { error: { code, message, details } }. Caching: Cache-Control headers, ETag for conditional GET. Pagination: cursor-based or offset in query params. Versioning: /api/v1/ URL prefix. Auth: Bearer token in Authorization header. Never GraphQL patterns (schema, resolvers, queries, mutations, errors array).'

GraphQL CLAUDE.md template: '# API (GraphQL). Schema: types, queries, mutations in SDL files. Endpoint: POST /graphql (single endpoint). Resolvers: implement schema fields, return typed data. Errors: always 200 HTTP, errors in response body errors array with extensions.code. Caching: Apollo Client normalized cache, @cacheControl directive for TTL. Auth: context from request headers, check in resolvers. Subscriptions: WebSocket for real-time. Never REST patterns (resource URLs, HTTP status codes for errors, Cache-Control headers).'

The templates capture the paradigm boundary: REST = HTTP-native (URLs, status codes, cache headers). GraphQL = schema-native (types, resolvers, errors array, normalized cache). The negative rules prevent paradigm bleeding — the most subtle and confusing category of AI-generated errors. Copy the template for your API paradigm.

Comparison Summary

Summary of REST vs GraphQL AI rules.

  • Design: REST = resource URLs + HTTP methods vs GraphQL = schema types + single endpoint
  • Fetching: REST = fixed response, may over/under-fetch vs GraphQL = client specifies exact fields
  • Errors: REST = HTTP status codes vs GraphQL = always 200, errors in body array
  • Caching: REST = HTTP Cache-Control + CDN vs GraphQL = normalized client cache + persisted queries
  • Partial success: GraphQL supports it natively. REST: one error per response
  • Type safety: GraphQL schema = typed contract. REST: OpenAPI spec for equivalent typing
  • Subscriptions: GraphQL has native subscription support. REST: use SSE or WebSocket separately
  • Templates: paradigm boundary rules prevent the most confusing AI-generated errors