Markdown Editor

GraphQL Schema Specification Template

GraphQL API spec: types, queries, mutations, subscriptions, connections, nullability, errors, auth, cost limits, federation

Arsitektur & Desain SistemLengkapGraphQL specification (September 2025 edition)graphqlschemaspecmath

Cara menggunakan: Use it for any GraphQL API alongside the SDL file in the repository. Fill the overview, nullability policy and naming conventions first, since they are hard to change after clients ship. The main pitfall is unbounded queries: set depth and cost limits before going public.

Pratinjau

GraphQL Schema Specification: [API or graph name]

Specification of one GraphQL API: schema overview, types, queries, mutations, subscriptions, pagination, errors, nullability, naming, deprecation, authorisation, query cost limits, persisted queries, federation, performance and evolution. Written against the GraphQL specification, September 2025 edition (spec.graphql.org), with the widely used cursor connections convention for pagination. Replace every [bracketed] value and every row marked Example. Worked examples use a bookshop catalogue and ordering graph.

Document control

FieldValue
Document ID[API-GQL-001]
Graph / API name[bookshop-graph]
Schema version or tag[2026-03-14 / commit abc1234]
GraphQL spec editionSeptember 2025
Endpoint[https://api.example.com/graphql]
Version[0.1]
Status[Draft / In review / Approved / Superseded]
Owner[@graph-team]
Classification[Internal / Partner / Public]
Last updatedYYYY-MM-DD

Approvals

RoleNameDecisionDate
Graph owner / API lead[@name][Approved / Approved with comments / Rejected]YYYY-MM-DD
Security reviewer[@name][Decision]YYYY-MM-DD
Client representative[@name][Decision]YYYY-MM-DD

Revision history

VersionDateAuthorChange
[0.1]YYYY-MM-DD[@author]Initial draft
[0.2]YYYY-MM-DD[@author]Added subscriptions and persisted queries

Contents

  1. Introduction
  2. Schema overview
  3. Types
  4. Queries
  5. Mutations
  6. Subscriptions
  7. Nullability
  8. Pagination
  9. Naming conventions
  10. Deprecation and evolution
  11. Errors
  12. Authorisation
  13. Query cost and depth limits
  14. Persisted queries and transport
  15. Federation and composition
  16. Performance
  17. Observability
  18. Schema governance
  19. Glossary and references

1. Introduction

1.1 Purpose

[Example: The bookshop graph gives the web shop and mobile apps one API for browsing the catalogue, managing baskets and placing orders. It replaces four REST backends-for-frontends.]

1.2 Consumers

ClientTypeMain operationsContact
Example: Web shopFirst-party webCatalogue browse, basket, checkout@web-team
Example: iOS and Android appsFirst-party mobileSame as web, plus order tracking subscription@mobile-team
Example: Partner bookstoresThird-partyRead-only catalogue and stock@partner-success
[Client][Type][Operations][@contact]

1.3 Design principles

  • Demand-oriented: the schema models what client screens need, expressed in domain language, not the shape of backing services.
  • One graph: one schema per organisation area, so clients do not stitch data together themselves.
  • Evolve, do not version: additive change plus deprecation; no /v2 endpoint.
  • Secure by default: every field is authorised server-side, regardless of which client asks.

1.4 Out of scope

[Example: Warehouse operations and finance reporting stay on their own APIs; the graph does not expose admin mutations.]

2. Schema overview

The diagram shows the main object types and their relationships.

AreaRoot fieldsOwning teamBacking service
Example: Cataloguebook, books, author, searchTeam Cataloguecatalogue-service
Example: Basketviewer.basket, addToBasketTeam Checkoutbasket-service
Example: Ordersviewer.orders, placeOrder, orderStatusChangedTeam Ordersorder-service
[Area][Fields][Team][Service]

3. Types

Full schema: [link to schema.graphql]. The type system uses the kinds defined by the GraphQL specification: scalars, objects, interfaces, unions, enums and input objects.

3.1 Custom scalars

Every custom scalar MUST declare @specifiedBy with a URL to its format.

"An instant in time, RFC 3339 format in UTC, e.g. 2026-03-14T09:30:00Z."
scalar DateTime @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")

"ISO 4217 currency code, e.g. EUR."
scalar CurrencyCode @specifiedBy(url: "https://www.iso.org/iso-4217-currency-codes.html")

3.2 Core object types and interfaces

Objects that clients may need to refetch implement the Node interface with a globally unique, opaque id. Money is always an object with a decimal string and a currency, never a float. Enums are used for closed sets of values that clients branch on; clients MUST handle unknown enum values gracefully because new values are added without a new version. Every type and field carries a description string, which becomes the reference documentation.

"An object with a globally unique ID that can be refetched with Query.node."
interface Node {
  id: ID!
}

type Money {
  "Decimal amount as a string to avoid floating-point errors, e.g. \"12.90\"."
  amount: String!
  currency: CurrencyCode!
}

type Book implements Node {
  id: ID!
  isbn13: String!
  title: String!
  authors: [Author!]!
  price: Money!
  publishedOn: String
  stock: StockLevel
}

enum StockLevel {
  IN_STOCK
  LOW_STOCK
  OUT_OF_STOCK
}

3.3 Type register

TypeKindDescriptionOwner
Example: BookObject (implements Node)A sellable edition identified by ISBN-13Team Catalogue
Example: SearchResultUnion (Book, Author)One hit in a catalogue searchTeam Catalogue
Example: PaymentMethodInputInput object (@oneOf)Exactly one of card token or gift card codeTeam Checkout
[Type][Kind][Description][Owner]

4. Queries

type Query {
  "Refetch any object by its global ID."
  node(id: ID!): Node
  book(id: ID!): Book
  books(first: Int, after: String, filter: BookFilter): BookConnection!
  search(text: String!, first: Int = 20, after: String): SearchResultConnection!
  "The authenticated customer, or null for anonymous callers."
  viewer: Viewer
}
FieldArgumentsAuthCostNotes
Example: booksfirst (max 50), after, filterPublic1 + firstFilter by genre, author, price range
Example: viewernoneCustomer token1Entry point for all personal data
[Field][Arguments][Auth][Cost][Notes]

5. Mutations

Conventions:

  • Name mutations verb first: addToBasket, placeOrder, cancelOrder.
  • Each mutation takes a single argument input: <MutationName>Input! and returns <MutationName>Payload!.
  • Payloads return the changed object and a list of typed userErrors for expected failures.
  • Mutations with side effects accept a clientMutationId or an idempotency key in the input.
input PlaceOrderInput {
  basketId: ID!
  paymentMethod: PaymentMethodInput!
  idempotencyKey: String!
}

input PaymentMethodInput @oneOf {
  cardToken: String
  giftCardCode: String
}

type PlaceOrderPayload {
  order: Order
  userErrors: [PlaceOrderError!]!
}

type PlaceOrderError {
  code: PlaceOrderErrorCode!
  message: String!
  "Path to the input field that caused the error, e.g. [\"paymentMethod\", \"cardToken\"]."
  field: [String!]
}

enum PlaceOrderErrorCode {
  BASKET_EMPTY
  ITEM_OUT_OF_STOCK
  PAYMENT_DECLINED
}

type Mutation {
  placeOrder(input: PlaceOrderInput!): PlaceOrderPayload!
}

6. Subscriptions

SubscriptionEvent sourceAuthDeliveryLimits
Example: orderStatusChanged(orderId: ID!)OrderStatusChanged broker eventCustomer token, must own orderAt most once; client refetches on reconnect5 active per connection
[Subscription][Source][Auth][Guarantee][Limits]
type Subscription {
  orderStatusChanged(orderId: ID!): Order!
}

Transport: [Example: GraphQL over WebSocket using the graphql-transport-ws subprotocol; connection authenticated in the connection_init payload].

7. Nullability

RulePolicy
Object fields backed by another serviceNullable, so one failing service does not null the parent
Identifiers and fields stored with the objectNon-null
Lists[Item!]!: the list and its items are non-null; an empty list means none
ArgumentsNon-null only when required; optional arguments have defaults where sensible
Changing nullabilityOutput: non-null to nullable is breaking for clients; input: nullable to non-null is breaking

8. Pagination

type BookConnection {
  edges: [BookEdge!]!
  pageInfo: PageInfo!
  "Approximate; may be omitted for expensive filters."
  totalCount: Int
}

type BookEdge {
  cursor: String!
  node: Book!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
  • first default [20], maximum [50]; larger values return a validation error.
  • Cursors are opaque, base64-encoded and valid for at least [24 hours].
  • Backward pagination (last, before) is supported only where [listed].

9. Naming conventions

ElementConventionExample
Types, interfaces, unions, enums, inputsPascalCaseBook, SearchResult, PlaceOrderInput
Fields and argumentscamelCasepublishedOn, isbn13
Enum valuesSCREAMING_SNAKE_CASEOUT_OF_STOCK
Mutationsverb + nouncancelOrder
Input types<Mutation>InputCancelOrderInput
Payload types<Mutation>PayloadCancelOrderPayload
Boolean fieldsis / has / can prefixisGift, canCancel
Subscriptionsnoun + past-tense verborderStatusChanged

Names come from the domain's ubiquitous language, never from backing service or table names.

10. Deprecation and evolution

type Book implements Node {
  id: ID!
  "Use price.amount and price.currency."
  priceInCents: Int @deprecated(reason: "Use `price`. Removal after 2026-12-31.")
  price: Money!
}
Schema coordinateDeprecated onReplacementUsage last 30 daysRemoval dateStatus
Example: Book.priceInCentsYYYY-MM-DDBook.price1.2% of operations (iOS 5.x)YYYY-MM-DDWaiting for app adoption
[Type.field]YYYY-MM-DD[Replacement][Usage]YYYY-MM-DD[Status]

Policy: [Example: minimum 90 days after deprecation and two app release cycles; removal requires zero usage from first-party clients for 14 days].

11. Errors

CategoryWhere it appearsExample
Validation of the documentTop-level errors, no dataUnknown field pricee
Business outcome expected by the clientPayload userErrors or union result typeITEM_OUT_OF_STOCK
Authorisation failure on a fieldTop-level errors with extensions.code = "FORBIDDEN", field nullReading another customer's order
Downstream failureTop-level errors with extensions.code = "UNAVAILABLE", partial dataStock service timeout
{
  "data": {"book": {"title": "Dune", "stock": null}},
  "errors": [{
    "message": "Stock information is temporarily unavailable.",
    "path": ["book", "stock"],
    "extensions": {"code": "UNAVAILABLE", "traceId": "4bf92f35"}
  }]
}

Error messages MUST NOT include stack traces or internal hostnames. The list of extensions.code values is fixed: [UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, BAD_USER_INPUT, RATE_LIMITED, UNAVAILABLE, INTERNAL].

12. Authorisation

RuleImplementation
AuthenticationOAuth 2.0 bearer token validated at the gateway; anonymous allowed for catalogue only
Object-level checksResolvers call the owning service, which checks ownership (order.customerId == token.sub)
Field-level checksSensitive fields (Customer.email) require scope customer:read:pii
IntrospectionDisabled for public traffic in production; enabled for authenticated developers
node(id:)Applies the same checks as the typed field; never a bypass

13. Query cost and depth limits

Cost is calculated statically before execution. Each field costs 1; a connection multiplies its children by its first argument:

cost(f)=1+nf∑c∈children(f)cost(c)\text{cost}(f) = 1 + n_f \sum_{c \in \text{children}(f)} \text{cost}(c)

where nfn_f is the page size for connections and 1 otherwise.

LimitValueResponse when exceeded
Maximum depth[10]Validation error, extensions.code = "QUERY_TOO_DEEP"
Maximum cost per operation[1,000]Validation error, QUERY_TOO_COMPLEX
Cost budget per client per minute[50,000]RATE_LIMITED, with Retry-After header
Maximum aliases per operation[30]Validation error
Request body size[100 KB]HTTP 413

14. Persisted queries and transport

TopicDecision
HTTP transportPOST application/json for all operations; GET allowed for persisted queries to enable caching. Follows the GraphQL over HTTP working draft.
Persisted queriesFirst-party clients register operations at build time; server accepts only known hashes from first-party client IDs
Third-party clientsArbitrary operations allowed, subject to section 13 limits
CachingCache-Control set per operation from the lowest field max-age; personalised data is private
Batching[Not supported / Up to 5 operations]

15. Federation and composition

SubgraphOwnerEntities ownedEntities extendedRouter
Example: catalogueTeam CatalogueBook (key id), Authornone[Router product and version]
Example: inventoryTeam StocknoneBook.stockSame
Example: ordersTeam OrdersOrderCustomer.ordersSame
[Subgraph][Team][Entities][Extensions][Router]

Composition checks run in CI; a subgraph change that breaks composition or an active client operation is blocked.

16. Performance

  • Resolvers that load by key use a per-request batching loader (the DataLoader pattern) so books { authors } makes one author call per request, not one per book.
  • Loaders cache only within one request; cross-request caching uses the services' own caches.
  • Target latency: p95 [250 ms] for persisted first-party queries, p99 [800 ms].
  • Resolver timeouts: [2 s] per downstream call; the field returns null with an UNAVAILABLE error when exceeded.

The sequence below shows batched loading for a list query.

17. Observability

SignalWhat is captured
MetricsRequests, errors and latency per operation name and client name; resolver latency for top fields
TracesOne span per operation, child spans per downstream call; trace ID in error extensions
Field usageField-level usage per client, retained [90 days], used for deprecation decisions
LogsOperation name, hash, client, duration, error codes; never variables containing personal data

18. Schema governance

CheckToolBlocking
SDL lint (naming, descriptions on all types and fields)[Tool]Yes
Breaking change detection against production schema[Tool]Yes, unless approved
Validation of registered client operations against new schema[Tool]Yes
Schema review by graph ownersPull requestYes for new root fields

Change process: a schema change starts as a pull request against the SDL file, owned by the team that owns the affected types. The graph owners review new root fields, new cross-team relationships and anything that changes nullability or authorisation. Approved changes are published to the schema registry, which notifies client teams of new deprecations.

Clients must send an operation name and a client name header ([x-client-name]) so usage can be attributed.

19. Glossary and references

TermMeaning
SDLSchema definition language, the textual form of a GraphQL schema
Schema coordinateA string identifying a schema element, such as Book.price
ConnectionCursor-paginated list wrapper with edges and page info
Persisted queryAn operation registered in advance and called by hash or ID
SubgraphA separately owned schema composed into the supergraph

References: GraphQL specification September 2025 (https://spec.graphql.org/September2025/); Relay cursor connections specification; GraphQL over HTTP working draft; [federation documentation used].

Standard and licence note

Structure aligned with the GraphQL specification, September 2025 edition (https://spec.graphql.org/), for the type system, directives (@deprecated, @specifiedBy, @oneOf), schema coordinates, error and response format. Pagination follows the Relay cursor connections convention; federation and the GraphQL over HTTP transport are referenced as separate documents. This template is original text written for this collection and reproduces no text from those specifications; consult them for normative requirements. Template licence: MIT.

Templat terkait