Relay and GraphQL

Now we have everything in order, we can (re)start implementing GraphQLand Relay.

I highly advise you to watch the 4 first chapters from How To Graph QL - Basics and then some reading at Relay Documentation 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) and contains what GraphQL will look for.

It'll usually have 3 root types: QueryMutation and Subscription. If we set a CRUD (Create, Read, Update, Delete) 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

// 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

// 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;
// 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.

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
// 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 &lt;App query={props} /&gt;;
    }

    return &lt;Text&gt;loading&lt;/Text&gt;;
  }}
/&gt;

);
};

export default AppQR;

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

// 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

// 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

// 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”
},

// package.json

"scripts: {

“update-schema”: “yarn --cwd packages/server update-schema”,

},

Relay

The Enviroment configuration shall be done as the following:

// 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;
// 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

“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

// 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

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.

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Further reading

☞ A Beginner’s Guide to GraphQL

☞ GraphQL with React: The Complete Developers Guide

☞ How to create a simple CRUD App using GraphQL and Node.js

☞ Node, Express, PostgreSQL, Vue 2 and GraphQL CRUD Web App

☞ Developing and Securing GraphQL APIs with Laravel

☞ Building the New facebook.com with React, GraphQL and Relay

#graphql #react-native #node-js #web-development

Relay and GraphQL
13.20 GEEK