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 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: Query, Mutation 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.
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
// 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
// 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.
Top comments (0)