Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Graphql Relay Php Laravel Package

ivome/graphql-relay-php

PHP library for adding Relay support to GraphQL servers. Provides helpers for cursor-based connections, edges, page info, global object identification, and Relay-style mutations, making it easier to build spec-compliant GraphQL APIs.

Deep Wiki
Context7

Getting Started

  • Install with composer require ivome/graphql-relay-php. It is a companion to webonyx/graphql-php (historically ^14.0), so check your graphql-php version before pulling it in — this is the first thing that will bite you.
  • Everything is exposed through a single static facade: GraphQLRelay\Relay. The first files worth reading in the repo are src/Relay.php, src/Connection/Connection.php, src/Node/Node.php, src/Mutation/Mutation.php — one cluster per Relay concept.
  • The package is a port of graphql-relay-js. If a helper feels under-documented, the JS reference implementation's docs map almost one-to-one (connectionFromArray, globalIdField, mutationWithClientMutationId, nodeDefinitions).
  • The fastest first win: take an existing object type you already have (User, Post, whatever) and turn one of its list fields into a Relay connection. A ~30-line change exercises the whole toolchain: connection definitions, cursor encoding, and pageInfo.

Rough shape:

use GraphQLRelay\Relay;

$userType = new ObjectType([
    'name' => 'User',
    'fields' => [
        'id'   => Relay::globalIdField('User', fn($u) => $u['id']),
        'name' => ['type' => Type::string()],
    ],
]);

$userConnection = Relay::connectionDefinitions([
    'name'     => 'User',
    'nodeType' => $userType,
]);

// 'users' field then looks like:
// 'type'   => $userConnection['connectionType'],
// 'args'   => Relay::connectionArgs(),
// 'resolve' => fn($root, $args) => Relay::connectionFromArray($users, $args),

Then print the schema (GraphQL\Utils\SchemaPrinter::doPrint($schema)) and confirm you now have edges { node, cursor }, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }. If Relay's client compiler is happy with that output, your foundation is correct.

Implementation Patterns

Connections and pagination

  • Define each connection once with Relay::connectionDefinitions([...]) and reuse ['connectionType'] everywhere that list is exposed. Don't hand-roll edges/pageInfo; Relay's compiler hard-codes those field names and their nullability.
  • For small, fully-loaded lists: Relay::connectionFromArray($array, $args). For anything database-backed: Relay::connectionFromArraySlice($slice, $args, ['sliceStart' => $offset, 'arrayLength' => $total]).
  • Combine with SQL paging by translating first/after into LIMIT first + 1 OFFSET cursorToOffset($after). Feed the resulting page into connectionFromArraySlice so the helper can compute hasNextPage/hasPreviousPage and edge cursors for you.

Node and global IDs

  • Call Relay::nodeDefinitions($idFetcher, $typeResolver) once at schema boot. The $idFetcher receives the opaque global ID; call Relay::fromGlobalId($id) inside to get ['type' => ..., 'id' => ...], then dispatch on the type.
  • The $typeResolver receives the object your fetcher returned and maps it to the corresponding object type. Keeping fetch and type-resolution in one place is what keeps node(id:) consistent with your typed fields.
  • Use Relay::globalIdField('User', $getter) for every id field. Mixing raw integer IDs and global IDs on the same schema is a reliable way to get silent cache corruption on the client — refs stop resolving and updates disappear.

Mutations

  • Prefer Relay::mutationWithClientMutationId([...]) over hand-written mutation fields. It wraps your payload inside input { ... } and echoes clientMutationId, which is what makes Relay's optimistic updates and mutation tracking work.
  • Standard split: mutateAndGetPayload performs the write and returns a plain payload array; outputFields re-exposes each field with a resolver that reads from that payload. Keep input and output field lists small and strongly typed so the client compiler can validate them.
  • Don't reach for a "simple" hand-rolled mutation as a shortcut. The moment optimistic UI is involved, a missing clientMutationId will make the mutation appear to do nothing on the client even though the server succeeded.

Schema assembly

  • All the helpers return field/type configs, not a Schema object. You still assemble GraphQL\Type\Schema yourself, so the library drops cleanly into an existing schema, type registry, or middleware stack.
  • For "give me N nodes by ID" queries, use Relay::pluralIdentifyingRootField(...) rather than issuing N separate node(id:) round-trips.

Day-to-day workflow with relay-compiler

  • Treat the PHP schema as the source of truth. After any helper change, dump the schema to a .graphql file and run relay-compiler against it. Misused helpers (missing pageInfo, wrong cursor nullability, wrong arg types) surface as compiler errors instead of runtime nulls.

Gotchas and Tips

  • Staleness is the biggest risk. The last release was April 2021 and it targets webonyx/graphql-php 14.x. On graphql-php 15+ you'll likely hit type-hint and config-shape mismatches around FieldDefinition/EnumType construction. Either pin graphql-php or fork the three or four helper files you actually use — the library is small enough that vendoring is viable.
  • Cursors are offsets, not identities. Relay::offsetToCursor(3) is literally base64("arrayconnection:3"). Insert or delete a row and every subsequent cursor shifts. Never persist them; never compare them across requests. If you need stable cursors, write your own encoder and skip the connection helpers for that field.
  • sliceStart must be absolute. connectionFromArraySlice wants the offset of the first element within the full list, not 0 per page. Passing 0 is the classic bug that makes hasPreviousPage lie and restarts cursors on page 2.
  • hasNextPage off-by-one. When feeding from SQL, fetch $first + 1 rows and set arrayLength to the total; the helper handles the edge case. connectionFromArray avoids the issue entirely but requires the whole list in memory.
  • Global ID type names are baked into the contract. toGlobalId('User', 1) embeds the string "User" into every ID the client has cached. Renaming a type is a breaking change for existing clients — treat it like a database migration.
  • nodeDefinitions returns an interface plus field configs; you wire them in yourself. Forgetting to add the node field to your query type is the most common "why isn't refetch working" bug — nothing is auto-registered.
  • Debugging checklist. SchemaPrinter::doPrint($schema) and a round-trip through GraphQL\Utils\BuildSchema will expose almost everything. Diff the printed schema against what relay-compiler generated before suspecting the client.
  • Extension points are everywhere because everything is static. Connection/cursor helpers are pure functions over arrays — wrapping them (MyRelay::connection(...)) to add stable cursors, DTO support, or caching is easy. nodeDefinitions's $idFetcher is the natural place to add dataloader-style batching, since every node(id:) call funnels through it.
  • Propagate context explicitly. Pass your request context (auth, tenant, DB connection) through every mutateAndGetPayload and $idFetcher. Re-deriving authorization inside each resolver is exactly where Relay-style ID fetching tends to get it wrong.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console