Relay is a technology that’s very opinionated, structured, and it ensures that you’re using your GraphQL server in the correct way.

GraphQL is a very powerful technology that enables us to build better APIs. GraphQL is a query language that helps us avoid unnecessary requests to our servers, reduces the over-fetching and under-fetching requests, and only gets exactly the data that we need. The technology has been growing in usage, the adoption of GraphQL in different languages is getting massive and the future is really bright for those who want to build GraphQL APIs.

Relay is a powerful JavaScript framework for declaratively working with GraphQL and building data-driven React applications. It’s not the most used GraphQL client by the community and there are a few reasons for that. One of them being that Relay is more structured and opinionated than other frameworks, the documentation is not very intuitive and the community itself is not very large. Although Apollo has a few advantages over Relay, mainly related to community and documentation, Relay has some advantages that make it really special.

Relay is recommended to use in the frontend to have more structured, modular, future-proofed applications that can scale easily to millions of users. The first thing that needs to be implemented in order to use Relay is to make a Relay-compatible GraphQL server. That’s what we’re going to do now.

GraphQL server specification

Relay works in an elegant way when handling caching and data fetching, it’s one of the biggest advantages that it has over other GraphQL clients.

Relay has something called the GraphQL Server Specification in its documentation, this guide shows the conventions that Relay makes about a GraphQL server in order to work correctly.

When creating a new GraphQL server that’s going to work with Relay, we need to make sure that it follows these principles:

A Node object

The Node interface is used for refetching an object, as the documentation says:

The server must provide an interface called Node. That interface must include exactly one field, called id that returns a non-null ID.This id should be a globally unique identifier for this object, and given just this id, the server should be able to refetch the object.

The Node interface is very important for the GraphQL schema to have a standard way of asking for an object using its ID.

Another thing that needs to be implemented is the Node root field. This field takes only one non-null globally unique ID as an argument:

If a query returns an object that implements Node, then this root field should refetch the identical object when the value returned by the server in the Node‘s id field is passed as the id parameter to the node root field.

How to page through connections

Pagination was always a pain point in APIs and there’s no standard way of implementing it correctly. Relay handles this problem very well by using the GraphQL Cursor Connections Specification:

{
  podcasts {
    name
    episodes(first: 10) {
      totalCount
      edges {
        node {
          name
        }
        cursor
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
}

The spec proposes a pattern called connections, and in the query, the connection provides a standard way for slicing and paginating the results. The connections also provide a standard way for responses providing cursors, a way of telling the client when more results are available.

Predictable mutations

Mutations should be using the input type in order to make them predictable and structured in a standard way:

input AddPodcastInput {
  id: ID!
  name: String!
}

mutation AddPodcastMutation($input: AddPodcastInput!) {
  addPodcast(input: $input) {
    podcast {
      id
      name
    }
  }
}

Making a GraphQL server compatible with Relay will assure that we have a well-structured, performant, and scalable GraphQL API that can be easily scalable to millions.

Getting started

Now that we know what the three principles that Relay expects our GraphQL server to provide, let’s create an example that follows the GraphQL Server Specification and see how it works in practice.

The first thing that we’re going to do is create a new project and install some dependencies:

yarn add @koa/cors @koa/router graphql graphql-relay koa koa-bodyparser koa-graphql koa-helmet koa-logger nodemon

Now we are going to add some dev dependencies:

yarn add --dev @types/graphql-relay @types/koa-bodyparser @types/koa-helmet @types/koa-logger @types/koa__cors @types/node ts-node typescript

After installing all these dependencies, we are now ready to create our GraphQL server. We are going to create a folder called src and here are the files for our example app:

-- src
  -- graphql.ts
  -- index.ts
  -- NodeInterface.ts
  -- schema.ts
  -- types.ts
  -- utils.ts
-- nodemon.json
-- tsconfig.json

Let’s create a file called tsconfig.json in order to use TypeScript in this project. We will put the following code inside this file:

{
  "compilerOptions": {
    "lib": ["es2016", "esnext.asynciterable"],
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "baseUrl": "."
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.spec.ts"]
}

In this example, we’re going to use nodemon to watch our src directory and restart our server every time a change is detected inside our directory. Inside the nodemon.json file, put the following code:

{
  "watch": ["src"],
  "ext": "ts",
  "ignore": ["src/**/*.spec.ts", "src/types/**/*.d.ts"],
  "exec": "ts-node ./src/index.ts"
}

Inside our index.ts file, we are going to put the following code:

import Koa from "koa";
import cors from "@koa/cors";
import Router from "@koa/router";
import bodyParser from "koa-bodyparser";
import logger from "koa-logger";
import helmet from "koa-helmet";
import graphqlHTTP from "koa-graphql";

import schema from "./schema";

const app = new Koa();
const router = new Router();

const graphqlServer = graphqlHTTP({ schema, graphiql: true });
router.all("/graphql", bodyParser(), graphqlServer);

app.listen(5000);
app.use(graphqlServer);
app.use(logger());
app.use(cors());
app.use(helmet());
app.use(router.routes()).use(router.allowedMethods());

For now, we just created a simple GraphQL API using Koa. We imported our GraphQL schema from our schema.ts file and now we are going to create the types that we’re going to need for our server to work properly.

Inside our types.ts is where we’re going to put the following code:

export class IUser {
  id: string;
  firstName: string;
  lastName: string;
  constructor(data) {
    this.id = data.id;
    this.firstName = data.firstName;
    this.lastName = data.lastName;
  }
}

We’re also going to create a file called utils.ts and create an empty array called users:

export const users = [];

We just created the type for our user. Now, inside our schema.ts file, we are going to create our GraphQL schema, let’s put the following code inside this file:

import { GraphQLSchema } from "graphql";
import { QueryType, MutationType } from "./graphql";

const schema = new GraphQLSchema({
  query: QueryType,
  mutation: MutationType,
});

export default schema;

Inside our graphql.ts file we are going to create our queries. Let’s create a variable called QueryType and import the NodeField from our NodeInterface.ts file:

import {
  GraphQLObjectType,
  GraphQLInt,
  GraphQLString,
  GraphQLNonNull,
} from "graphql";

import { NodeField, NodesField } from "./NodeInterface";

export const QueryType = new GraphQLObjectType({
  name: "Query",
  description: "The root of all... queries",
  fields: () => ({
    node: NodeField,
    nodes: NodesField,
  }),
});

Now, we have the basics of our GraphQL server. Let’s implement the Node field and see how it works. Let’s go now to our NodeInterface.ts file and type some code.

We’re going to import functions from graphql-relay, nodeDefinitions, and fromGlobalId:

  • The nodeDefinitions functions help us to map globally defined IDs into actual data objects. The first argument of this function receives the fromGlobalId function and the second argument is used to read the type of the object using fromGlobalId function
  • The fromGlobalId function will retrieve the object using its global ID

#graphql #javascript #react #relay #developer

Making a GraphQL Server Compatible with Relay
1.70 GEEK