Fullstacking: Relay + GraphQL
Tags: graphql, reactnative, node
Published: 2019-09-04 12:29:52

Now we have everything in order, we can (re)start implementing **GraphQL** and **Relay**.

I highly advise you to watch the 4 first chapters from [How To Graph QL - Basics](https://www.howtographql.com/basics/) and then some reading at [Relay Documentation](https://relay.dev/docs/en/introduction-to-relay) to understand some concepts of GraphQL and Relay.

## Setting Up GraphQL

First we shall define our `schema.graphql`. This file is written in [Schema Definition Language (SDL)](https://blog.graph.cool/graphql-sdl-schema-definition-language-6755bcb9ce51) and contains what GraphQL will look for.
It'll usually have 3 root types: **Query**, **Mutation** and **Subscription**. If we set a **CRUD** (**C**reate, **R**ead, **U**pdate, **D**elete) style API, we'll have
* **Query:** Reads
* **Mutation:** Creates, Update, Delete
* **Subscription:** Subscribes to these CRUD events

Besides root types, it'll also have some **"object"** types that will define your objects in the database.
In our case below, we're setting our `schema.graphql` with the **Product** type with a required (!) **id** and a **title**.
We're also setting a Query called **"product"** that needs an id and returns a **Product** type.
We can also set a **"products"** query that returns a **list of Products**

```javascript
// packages/server/data/schema.graphql
// and a copy in packages/app/data/schema.graphql
type Product {
  id: ID!
  title: String
}

type Query {
  product(id: ID!): Product
  products: [Product]
}
```

Now we have to write this **schema** as javascript so **Koa** (via `koa-graphql`) can use it as **instructions** (contract) to find data in our database.

You'll notice how some code is converted:
**!** as **GraphQLNonNull**
**ID** as **GraphQLID**
**String** as **GraphQLString**
an so on

```javascript
// packages/server/graphql/productType.js
const graphql = require('graphql');
const globalIdField = require('graphql-relay').globalIdField;

const {GraphQLObjectType, GraphQLString} = graphql;

const ProductType = new GraphQLObjectType({
  name: 'Product',
  fields: () => ({
    id: globalIdField('products'),
    title: {type: GraphQLString},
  }),
});

module.exports = ProductType;
```

```javascript
// packages/server/graphql/schema.js
const { 
      GraphQLSchema, 
      GraphQLObjectType, 
      GraphQLID, 
      GraphQLList, 
      GraphQLNonNull,
      } = require('graphql');
const fromGlobalId = require('graphql-relay').fromGlobalId;
const productGraphQLType = require('./productType');
const Product = require('../models/Product');

const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    product: {
      type: productGraphQLType,
      args: {id: {type: GraphQLNonNull(GraphQLID)}},
      resolve(parent, args) {
        return Product.findById(fromGlobalId(args.id).id);
      },
    },
    products: {
      type: GraphQLList(productGraphQLType),
      resolve() {
        return Product.find().lean();
      },
    },
  },
});

module.exports = new GraphQLSchema({
  query: Query,
});
```

You'll notice our **Resolve Functions**. They are functions that connects the **schema** to the **database**. Remember that the `Product` class imported from `'../models/Product` is created with **Mongoose** and that's how it accesses our MongoDB instance.

## React Native 

To require the data from React, we'll use `babel-plugin-relay/macro` to "translate" `graphql` into our request.
We'll also use a **High Order Component** called `<QueryRenderer>` to render our actual `<App>` with the data from Relay.
A **Query Renderer** component will use the following props:
* A configuration file `Environment`
* The query
* Variables used in the query 
* A render function that returns 3 cases: error, success and loading

```react
// packages/app/src/App.js
import React, {Fragment} from 'react';
import {Text} from 'react-native';
import graphql from 'babel-plugin-relay/macro';
import {QueryRenderer} from 'react-relay';

import Environment from './relay/Environment';

const App = ({query}) => {
  const {products} = query;

  return (
    <Fragment>
      <Text>Hello World! Product: {products[0].title}</Text>
    </Fragment>
  );
};

const AppQR = () => {
  return (
    <QueryRenderer
      environment={Environment}
      query={graphql`
        query AppQuery {
          products {
            id
            title
          }
        }
      `}
      variables={{}}
      render={({error, props}) => {
        console.log('qr: ', error, props);
        if (error) {
          return <Text>{error.toString()}</Text>;
        }

        if (props) {
          return <App query={props} />;
        }

        return <Text>loading</Text>;
      }}
    />
  );
};

export default AppQR;
```

However to make `babel-plugin-relay` work, you'll need to create this **script** to generate a `schema.json` file that'll be read by a **relay-compiler**

```javascript
// packages/server/scripts/updateSchema.js
#!/usr/bin/env babel-node --optional es7.asyncFunctions

const fs = require('fs');
const path = require('path');
const schema = require('../graphql/schema');
const graphql = require('graphql').graphql;
const introspectionQuery = require('graphql/utilities').introspectionQuery;
const printSchema = require('graphql/utilities').printSchema;

// Save JSON of full schema introspection for Babel Relay Plugin to use
(async () => {
  const result = await graphql(schema, introspectionQuery);
  if (result.errors) {
    console.error(
      'ERROR introspecting schema: ',
      JSON.stringify(result.errors, null, 2),
    );
  } else {
    fs.writeFileSync(
      path.join(__dirname, '../data/schema.json'),
      JSON.stringify(result, null, 2),
    );

    process.exit(0);
  }
})();

// Save user readable type system shorthand of schema
fs.writeFileSync(
  path.join(__dirname, '../data/schema.graphql'),
  printSchema(schema),
);
```
You'll need to change `babel.config.js` file as follows

```javascript
// packages/app/babel.config.js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['macros'], // add this
};
```
And you'll also need to run this `updateSchema.js` everytime you change your schema by using `yarn update-schema`

```javascript
// packages/server/package.json
...
  "scripts": {
    "start": "nodemon server.js",
    "update-schema": "babel-node --extensions \".es6,.js,.es,.jsx,.mjs,.ts\" ./scripts/updateSchema.js",
    "test": "jest"
  },
...
```
```javascript
// package.json
...
"scripts: {
   ...
   "update-schema": "yarn --cwd packages/server update-schema",
   ...
   },
...
```

## Relay

The Enviroment configuration shall be done as the following:
```javascript
// packages/app/src/relay/Environment.js
import {Environment, Network, RecordSource, Store} from 'relay-runtime';

import fetchQuery from './fetchQuery';

const network = Network.create(fetchQuery);

const source = new RecordSource();
const store = new Store(source);

const env = new Environment({
  network,
  store,
});

export default env;
```

```javascript
// packages/app/src/relay/fetchQuery.js
import {Variables, UploadableMap} from 'react-relay';
import {RequestNode} from 'relay-runtime';

export const GRAPHQL_URL = 'http://localhost:3000/graphql';

// Define a function that fetches the results of a request (query/mutation/etc)
// and returns its results as a Promise:
const fetchQuery = async (request, variables) => {
  const body = JSON.stringify({
    name: request.name, // used by graphql mock on tests
    query: request.text, // GraphQL text from input
    variables,
  });
  const headers = {
    Accept: 'application/json',
    'Content-type': 'application/json',
  };

  const response = await fetch(GRAPHQL_URL, {
    method: 'POST',
    headers,
    body,
  });

  return await response.json();
};

export default fetchQuery;
```

You'll also have to configure `relay-compiler` by adding and running `yarn relay`
```json
"scripts": {
  "relay": "relay-compiler --src ./src --schema ./schema.graphql"
}
```

## KoaJS

Finally, to serve our **GraphQL server** into a single endpoint, we'll use `koa-mount` and `koa-graphql` using our `schema.js`

```javascript
// packages/server/server.js
const Koa = require('koa');
const mount = require('koa-mount');
const graphqlHTTP = require('koa-graphql');
const schema = require('./graphql/schema');

const databaseUrl = "mongodb://127.0.0.1:27017/test";
mongoose.connect(databaseUrl, { useNewUrlParser: true });
mongoose.connection.once("open", () => {
  console.log(`Connected to database: ${databaseUrl}`);
});

const app = new Koa();

app.use(
  mount(
    '/graphql',
    graphqlHTTP({
      schema: schema,
      graphiql: true,
    }),
  ),
);

app.listen(3000, () =>
  console.log("Server is running on http://localhost:3000/")
);
```

## Running

You'll need to install all dependencies first.

* Inside `app` package:

`yarn add react-relay`
`yarn add --dev graphql graphql-compiler relay-compiler relay-runtime babel-plugin-relay`

* Inside `server` package:

`yarn add graphql koa-mount koa-graphql graphql-relay graphql-compiler`
`yarn add --dev @babel/core @babel/node`

And run our set scripts:
`yarn relay`
`yarn update-schema`

Then you might run some yarn commands that were set in last post.

`yarn start:server` (don't forget to `sudo service mongod start`)
`yarn start:app`
`yarn android`

If you get `Network error` with server and mongodb running correctly, you'll need to **redirect** some ports with `adb reverse tcp:<portnumber> tcp: <portnumber>`
You may want to add the following script in `packages/app/scripts/redirectPorts.sh` and `"redirect": "sh ./packages/app/scripts/redirectPorts.sh"` in the root `package.json` to make things easier with a `yarn redirect`
```shell
adb reverse tcp:8081 tcp:8081
adb reverse tcp:3000 tcp:3000
adb reverse tcp:5002 tcp:5002

adb -d reverse tcp:8081 tcp:8081
adb -d reverse tcp:3000 tcp:3000
adb -d reverse tcp:5002 tcp:5002

adb -e reverse tcp:8081 tcp:8081
adb -e reverse tcp:3000 tcp:3000
adb -e reverse tcp:5002 tcp:5002
```

That's it. You should be seeing "Stampler" in your view.

## References:
* [A reference implementation of GraphQL for JavaScript](https://github.com/graphql/graphql-js)
* [Relay - a JavaScript framework for building data-driven React applications](https://relay.dev/docs/en/introduction-to-relay)
* [The Fullstack Tutorial for GraphQL](https://www.howtographql.com/)