The first core assumption that Relay makes about a GraphQL API is that is provides a mechanism for refetching an object. The following steps will configure your GraphQL API to provide this mechanism.
Contents
- Include the Global ID Scalar
- Create a Node Type
- Resolve the
id
Field - Resolve the
node
andnodes
Root Fields - Conclusion
The GlobalIdScalar
helps to abstract the implementation of global identifiers from your application code by exporting a customised ID
scalar that automatically handles transformations at the interface layer of the GraphQL API.
Include the GlobalIdScalar
into a Nest module as in the example below.
import { Module } from '@nestjs/common'
import { GlobalIdScalar } from 'nestjs-relay'
@Module({ providers: [GlobalIdScalar] })
export class CommonModule {}
The global id scalar will override the default functionality of the ID
scalar that comes out of the box from nestjs/graphql
.
The NodeType
decorator helps to abstract the implementation of node types within your application code, and is a drop-in replacement for the ObjectType
decorator from @nestjs/graphql
.
The Ship
type class must also extend NodeInterface
, which will add the id
field to the schema.
import { NodeType, NodeInterface } from 'nestjs-relay'
@NodeType()
export class Ship extends NodeInterface {
@Field()
name: string;
}
The NodeType
decorator accepts the same argument signatures as the ObjectType
decorator from @nestjs/graphql
. The NodeType
decorator also enables Connections
to be aware of the name of the type.
At this point, your application's schema will contain the following changes:
"""An object with an ID"""
interface Node {
"""The ID of the object"""
id: ID!
}
type Ship implements Node {
id: ID!
name: String
}
Now that the id
field has been added to the Ship
type, we need to implement a custom resolver to ensure that a global id is resolved from the value we pass to it.
The GlobalIdFieldResolver
function automatically implements the resolver behaviour for us.
import { Resolver } from '@nestjs/graphql'
import { GlobalIdFieldResolver } from 'nestjs-relay'
import { Ship } from './ship.type'
@Resolver()
export class ShipResolver extends GlobalIdFieldResolver(Ship) {}
The default behaviour of the id
field resolver will serialize a global id from either a number
, string
or ResolvedGlobalId
.
A second argument can be provided to pass additional options to the id
field that you would normally find in the FieldResolver
decorator from @nestjs/graphql
. This is currently limited to the complexity
property, as the Relay specification requires this field to be named 'id'
and to be non-nullable.
Now that each object of the Ship
type can be uniquely identified, we can implement the node
and nodes
root fields. These root fields will allow a Relay client to refetch data by providing an id
.
The NodeFieldResolver
function automatically registers the node
and nodes
root fields in the GraphQL schema, allowing our application code to handle how to resolve each type.
import { Resolver } from '@nestjs/graphql'
import { NodeInterface, NodeFieldResolver, ResolvedGlobalId } from 'nestjs-relay'
@Resolver(NodeInterface)
export class NodeResolver extends NodeFieldResolver() {
resolveNode(resolvedGlobalId: ResolvedGlobalId) {
return null
}
}
The resolver should be configured to resolve the NodeInterface
; this enables it to return any schema type that implements the node interface.
The NodeFieldResolver
class requires that your application code implements the resolveNode
method, which should contain the logic for determining which type is being requested, then fetching the data for the specified object of that type.
import { Resolver } from '@nestjs/graphql'
import { NodeInterface, NodeFieldResolver, ResolvedGlobalId } from 'nestjs-relay'
import { ShipService } from './ship.service'
@Resolver(NodeInterface)
export class NodeResolver extends NodeFieldResolver() {
constructor(private shipService: ShipService) {
super()
}
resolveNode(resolvedGlobalId: ResolvedGlobalId) {
switch(resolvedGlobalId.type) {
case: 'Ship':
return this.shipService.findOneById(resolvedGlobalId.toString())
default:
return null
}
}
}
The ResolvedGlobalId
type has two properties - type
and id
- as well as the toString
and toNumber
helper methods so that your application code can remain free of the conversion logic.
const resolvedGlobalId = new ResolvedGlobalId({ type: 'Ship', id: '1' })
console.log(resolvedGlobalId.toString()) // '1'
console.log(resolvedGlobalId.toNumber()) // 1
At this point, your application's GraphQL schema will contain the following changes:
type Query {
"""Fetches an object given its ID"""
node(
"""The ID of an object"""
id: ID!
): Node
"""Fetches objects given their IDs"""
nodes(
"""The IDs of objects"""
ids: [ID!]!
): [Node]!
}