{"dataset":{"id":"00000000-0000-0000-0000-000000000019","slug":"graphql-best-practices","name":"GraphQL best practices","description":"Source-linked guidance on GraphQL fundamentals: the type system and SDL, query and mutation operations, resolver execution, cursor-based pagination, and nullability and error design. Scoped to specification-level concepts; not tied to any server framework or client library.","visibility":"public","version":1,"documentCount":5,"publisher":"Koskamo editorial","license":"Original summaries; linked sources retain their own terms.","reviewedAt":"2026-10-04T00:00:00.000Z","readOnly":true,"createdAt":"2026-10-04T00:00:00.000Z","updatedAt":"2026-10-04T00:00:00.000Z"},"total":5,"documents":[{"title":"Nullability by default and versionless evolution","sourceUrl":"https://graphql.org/learn/schema-design/","text":"GraphQL takes a strong opinion on avoiding versioning: because the server only returns data that was explicitly requested, new capabilities can be added as new types or new fields on existing types without a breaking change, which has led to the common practice of serving a versionless, continuously evolving API. Nullability follows the same resilience logic — in a GraphQL type system every field is nullable by default, because a networked service backed by databases and other services has many failure modes: a database could go down, an asynchronous action could fail, an exception could be thrown, or granular authorization could deny a single field. Defaulting to nullable means any of these result in just that field returning null rather than a complete failure of the request. Non-null variants make the opposite guarantee — the field will never return null — and if an error does occur, the previous parent field becomes null instead. When designing a schema, decide whether null is an appropriate value for a failed field; typically it is, and where it is not, use non-null types. Scope: GraphQL. Reviewed October 4, 2026.","id":"graphql-best-practices:Nullability by default and versionless evolution","version":1,"createdAt":"2026-10-04T00:00:00.000Z","updatedAt":"2026-10-04T00:00:00.000Z"},{"title":"Cursor-based pagination with connections","sourceUrl":"https://graphql.org/learn/pagination/","text":"The simplest way to expose a relationship between objects is a field returning a plural List type, but list fields that may return a lot of data should be paginated, starting with slicing arguments such as friends(first: 2). Offset-based pagination like friends(first: 2, offset: 2) has performance and security downsides on larger data sets, and offsets become ambiguous when records are inserted between requests, so cursor-based pagination — friends(first: 2, after: $friendCursor) — is the recommended model; opaque cursors can still encode an offset or ID internally, and base64-encoding them reinforces that clients must not rely on their format. Because a cursor is a property of the connection rather than the object, the field returns edges, each holding a cursor and the underlying node, which is also the natural place for edge-specific data like friendship time. A connection object wraps the edges with information about the connection itself: totalCount, and a pageInfo object with startCursor, endCursor, and hasNextPage so clients know when the list ends without an extra request. The Relay project publishes a formal cursor connection specification worth following whether or not you use Relay. Scope: GraphQL. Reviewed October 4, 2026.","id":"graphql-best-practices:Cursor-based pagination with connections","version":1,"createdAt":"2026-10-04T00:00:00.000Z","updatedAt":"2026-10-04T00:00:00.000Z"},{"title":"Execution and resolver functions","sourceUrl":"https://graphql.org/learn/execution/","text":"After a parsed document is validated, execution begins at the top-level Query, Mutation, or Subscription fields, and each field on each type is backed by a resolver function written by the server developer. In the reference implementation a resolver receives four arguments: obj (the previous object), args (the field's arguments), context (a value shared with every resolver, holding things like the logged-in user or database access), and info (field and schema details for advanced use). While a query could technically write data, mutation operations are conventionally used for side effects, and implementations are expected to execute mutation fields serially. Resolvers may be asynchronous — returning Promises, Futures, or Tasks — and GraphQL waits for them with optimal concurrency; a resolver can even return a list of Promises, each of which resolves concurrently before sub-fields continue. Many libraries let you omit trivial resolvers that just read a same-named property, and scalar coercion converts resolver output, such as internally numeric Enum values, into what the schema expects. Resolved values are collected bottom-up into a structure that mirrors the query and is sent to the client, typically as JSON. Scope: GraphQL. Reviewed October 4, 2026.","id":"graphql-best-practices:Execution and resolver functions","version":1,"createdAt":"2026-10-04T00:00:00.000Z","updatedAt":"2026-10-04T00:00:00.000Z"},{"title":"Queries, mutations, and query features","sourceUrl":"https://graphql.org/learn/queries/","text":"GraphQL supports three operation types — queries, mutations, and subscriptions — and a read operation starts at the query root operation type, then traverses a selection set of fields down to leaf values, which are Scalar or Enum types. The result returns under a top-level data key and mirrors the shape of the query, with any errors on a top-level errors key. Fields can return Object types, so one request can traverse related objects instead of making several roundtrips as in a classic REST architecture, and every field can take its own arguments. Aliases rename a result key so the same field can be fetched twice with different arguments. Dynamic values belong in variables, not string interpolation: replace the static value with $variableName, declare it in the operation (defaults such as $episode: Episode = JEDI are allowed), and pass values in a separate, usually JSON, dictionary. Fragments are reusable selection sets, inline fragments with type conditions reach fields on concrete types behind Interface or Union fields, and __typename reports the Object type at any point. The specification includes exactly two directives, @include(if: Boolean) and @skip(if: Boolean), which any spec-compliant server must support. Scope: GraphQL. Reviewed October 4, 2026.","id":"graphql-best-practices:Queries, mutations, and query features","version":1,"createdAt":"2026-10-04T00:00:00.000Z","updatedAt":"2026-10-04T00:00:00.000Z"},{"title":"The type system and SDL","sourceUrl":"https://graphql.org/learn/schema/","text":"Every GraphQL service defines a set of types that completely describe the data clients can query, and incoming requests are validated and executed against that schema. GraphQL has six kinds of named type definitions: Object, Scalar, Enum, Interface, Union, and Input Object types. Object types contain fields that specify output types, and those fields may take arguments that specify input types; all arguments are named rather than ordered, and they may declare defaults such as length(unit: LengthUnit = METER). The built-in Scalar types are Int, Float, String, Boolean, and ID, and custom scalars are allowed. The List ([]) and Non-Null (!) type modifiers change default behavior: String! promises a value whenever the field is queried, while [Episode!]! promises an array whose items are all non-null. Enum types represent leaf values limited to a finite set, Interface and Union types let one field output different concrete Object types, and Input Object types pass complex values into arguments. Schemas are discussed language-agnostically in the schema definition language (SDL), which supports descriptions for documentation and type system directives that alter validation and execution. Scope: GraphQL. Reviewed October 4, 2026.","id":"graphql-best-practices:The type system and SDL","version":1,"createdAt":"2026-10-04T00:00:00.000Z","updatedAt":"2026-10-04T00:00:00.000Z"}]}