Tutorial: Leveraging the Hasura platform, GraphQL and Apollo to build and deploy a fullstack react app
In this tutorial we are going to build a todo app from scratch. Our todo app will have the following features:
- A simple UI to list, add, delete and toggle todos
- Authentication
- Connecting the react app to a backend using GraphQL APIs
- Use the Apollo Client to make the GraphQL requests.
Note: We will be using the new Query and Mutation components that Apollo introduced in their 2.1.3 release of react-apollo. - Deployed on a publicly accessible URL
Table of contents
- Using the Hasura platform to create our backend
- Authentication
- Exploring the GraphQL APIs
- Building the react app
- Deploying the react app to a public URL
Pre-requisites
- Ensure that you have node.js installed on your machine.
- Install the hasura command line tool on your machine.
- Login or register on the Hasura platform by running
hasura login
in your command shell.
Using Hasura to create our backend
The Hasura platform provides backend features like Authentication and a Database with GraphQL APIs. Moreover, you can also use it to deploy your apps to the cloud.
Let’s start with a basic Hasura project
$ hasura quickstart hasura/base todo-app
The above command does the following:
- Clones a Hasura project into a directory called
base
- Creates a free Hasura cluster. In this case, the cluster is called
illumination52
. You can get information about your cluster by running the commandhasura cluster status
. - Initialises the
base
directory as agit
repository
Let’s now deploy this project on to the cluster to start building our backend
$ cd todo-app
$ git add . && git commit -m "Initial Commit"
$ git push hasura master
After you are done deploying this project. We can start building the backend for our todo app.
The API Console is basically a UI that helps you use and explore the backend features provided by the Hasura platform.
Open the API Console
by running the following inside the project directory
$ hasura api-console
Creating tables
To create a new table, head to the Data
tab and click on the Create Table
button
We will be creating a table todo
with the following columns
id
of typeInteger(auto-increment)
: This is the unique id associated with every todo.task
of typeText
: This will be the content of the todo.completed
of typeBoolean
: This is a boolean field indicating whether the todo is completed.user_id
of typeInteger
: This column will store the id of the user who has created this todo.
Our primary key will be id
Click on create
.
By default, every table created on the Hasura platform only has admin permissions set on it. This means that only admin users are allowed to fetch and modify data from tables by default.
Every user created using the Hasura platform’s authentication APIs are given a user
role by default. Since we are going to be using Hasura to add authentication to our react app as well, we need to add permissions to the todo
table. The permissions should allow users to fetch, add, update and delete their own todos. Fortunately, this also can be easily done on the API Console
Adding table permissions
Select the todo
table from the panel on the left. Click on the Permissions
tab.
Click on insert
and add the permissions as shown below
The above permission means that for every row inserted into this table, the user_id
should be the same as the id
of the Hasura user who is making the insert request.
Similarly, set permissions for select, update and delete
Note: The columns selected for select
permissions are the columns whose value a user should be allowed to fetch from the table. We allow select on all columns. Similarly, the selected columns for the update
permission are the columns whose data can be modified once they have been inserted into the table. In this case, we only allow update on completed
and task
.
Our modelling for the todo app is now complete. Next, we need to figure out authentication for our app.
Authentication
The Hasura platform provides instant authentication APIs to be used in our front-end app. You can check these out in the API Explorer
Let’s take a look at a simple signup
API to register a new user into our app with a username and password.
Click on SignUp
under the Username Password
In the image above, I have signed up with the username jaison
and password as secretpassword
. The response of this signup
looks like so:
{
"auth_token": "fdea373b0ec9065f207c3790f77bb18aea5345aa32ceb99",
"username": "jaison",
"hasura_id": 2,
"hasura_roles": [
"user"
]
}
auth_token
: is the session token for this user. We will be sending this token in our request header while making requests to ourtodo
table to identify as the userjaison
hasura_id
: is theid
of this user. This is theid
we will store asuser_id
in thetodo
table. This will be unique for every user created.hasura_roles
: are the roles associated with this user. As mentioned above, every new user created using the authentication APIs are given a default role ofuser
. You can add custom roles or add anadmin
role to this user. But that is beyond the scope of this tutorial.
Now that we have these APIs we can easily build a UI for our todo app. Having said that, we are actually NOT going to be building a UI for authentication and instead use the Auth UI Kit that Hasura provides for our app.
Auth UI Kit
The Auth UI kit is a ready made UI that handles authentication automatically for us. It runs on the url https://auth.<cluster-name>.hasura-app.io/ui
. In this case, it is https://auth.illumination52.hasura-app.io/ui
.
The Auth UI Kit does two major things for us
- Redirects to a specified URL after successful authentication.
- Sets the session of the user as a cookie.
So, all we have to do is show the Auth UI Kit with the url to our react app as the redirect URL and simply include the cookie in every request to the database.
We have created our database schema, figured out authentication and also set permissions on our database so that only authenticated users are allowed to get or modify data. Let’s take a look at how this works by inserting some data into the todo
table as the user we created above (username: jaison
, hasura_id: 2
)
Exploring the GraphQL APIs
In the API Console
click on the API Explorer
tab and select GraphQL
under the heading Data
from the panel on the left.
You can now use the GraphiQL interface to explore the various GraphQL endpoints available.
GraphQL Mutations
Let’s start by inserting data into the todo
table. The mutation to insert data will look like so
mutation ($objects: [todo_input!]){
insert_todo(objects: $objects) {
affected_rows
returning {
id
task
completed
user_id
}
}
}
And the variables would be
{
"objects": [
{
"task": "Task 1",
"completed": false,
"user_id": 2
}
]
}
Trying this out in the API Explorer
The query fails! This is because we have not added the session token in the header and since anonymous users are not allowed to insert data, the query fails.
Try the query again after adding the following headers
Authorization
: Bearer <session-token> (In this caseBearer fdea373b0ec9065f207c3790f77bb18aea5345aa32ceb99
)X-Hasura-Role
:user
And it succeeds!
Similarly, update mutations would be
mutation ($todoId: Int, $set: todo_input!){
update_todo(where: { id: { _eq: $todoId } } _set: $set) {
affected_rows
}
}
Variables:
{
"todoId": 1,
"set": {
"completed": true
}
}
Finally, delete mutations:
mutation delete_todo ($todoId: Int) {
delete_todo(where: {id: {_eq: $todoId}}) {
affected_rows
}
}
GraphQL Query
To get data from the table
query ($userId: Int) {
todo(where: { user_id: { _eq: $userId }}) {
id
completed
task
}
}
Variables
{
"userId": 2
}
Response would be
{
"data": {
"todo": [
{
"id": 1,
"completed": true,
"task": "Task 1"
}
]
}
}
Ok, now that we have our backend figured out. Let’s move on to our react code and stitch all of this up.
Building the react app
We are going to use create-react-app
as our react starter kit.
Install create-react-app
by running
$ npm install -g create-react-app
Create a new project
$ create-react-app app
cd into the app
directory and install dependencies
$ cd app
$ npm install
Run the app
$ npm run start
Your app will now be running at http://localhost:3000
The current version of create-react-app
comes with service workers enabled by default. Configuring and using service workers is a topic that is out of scope of this article. You can check out this blog post for insights into service workers.
Delete all the files inside thesrc
directory. We are going to start from scratch.
Next, create new directories called components
, graphQueries
and styles
inside the src
directory.
Installing Dependencies
We are going to be using the Apollo Client to make the GraphQL APIs from our react app
$ npm install --save apollo-boost graphql graphql-tag react-apollo
constants.js
Let’s start by creating a constants.js
file inside the src
directory
const clusterName = 'illumination52';
const GRAPHQL_URL = 'https://data.' + clusterName + '.hasura-app.io/v1alpha1/graphql';
const AUTH_URL = 'https://auth.' + clusterName + '.hasura-app.io';export {
GRAPHQL_URL,
AUTH_URL
};
clusterName
is the name of your cluster. In this case it isillumination52
.AUTH_URL
is the base url of the authentication APIs. It is always of the typehttps://auth.<cluster-name>.hasura-app.io
GRAPHQL_URL
is the url for the GraphQL API. It is always of the typehttps://data.<cluster-name>.hasura-app.io/v1alpha1/graphql
. We will be providing this url to the Apollo Client.
Index.js
Create a new index.js
file inside the src
directory with the following code
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import ApolloClient from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';import { GRAPHQL_URL } from './constants';const client = new ApolloClient({
link: createHttpLink({
uri: GRAPHQL_URL,
credentials: 'include'
}),
cache: new InMemoryCache({
addTypename: false
})
});ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
Here we are initialising our App with Apollo.
credentials: 'include'
is used to automatically add the cookies set by the Auth UI Kit to all the requests made by Apollo.addTypename: false
is added to prevent Apollo from automatically adding theaddTypename
to the queries to the GraphQL server.
App.js
This is the component that we are rendering from index.js
.
Although, using the Auth UI Kit sets the session in the cookie. We still need to get the user’s hasura_id
This is because we are going to be inserting rows in the todo
table, which is going to require a user_id
. We are going to use one of the authentication APIs to fetch the info of the logged in user. You can test this out in the API Explorer
It is basically a Get
request to https://auth.illumination52.hasura-app.io/v1/user/info
with the session token sent in the header. The response is the same as the response from the login/signup endpoint.
So our App.js
file will look like so
The flow of the code is such, hit the /v1/user/info
endpoint to get user information when the component mounts
- If this request responds with a non 200 response, redirect the user to
https://auth.<project-name>.hasura-app.io/ui?redirect_url=http://localhost:3000
(In this casehttps://auth.illumination52.hasura-app.io/ui?redirect_url=http://localhost:3000
) - If the request succeeds, then save the response as a global object.
- We are using the
constants.js
file we created earlier to hold this information. Add the following to yourconstants.js
file.
.......const REDIRECT_URL = 'http://localhost:3000'var userInfo = null;function saveUserInfo(info) {
userInfo = info;
}function getUserInfo() {
return userInfo;
}export {
saveUserInfo, getUserInfo,
GRAPHQL_URL,
AUTH_URL,
REDIRECT_URL
};
Run the app:
Now we have a basic react app with authentication.
Next, let’s create the todo components
GraphQL APIs
Let’s list down all of our GraphQL APIs in a file at graphQueries/todoQueries.js
so that we are defining these APIs at once place.
import gql from 'graphql-tag';const QUERY_TODO = gql`
query fetch_todos {
todo {
id
task
completed
}
}
`;const MUTATION_TODO_ADD = gql`
mutation insert_todo ($objects: [todo_input!]){
insert_todo(objects: $objects) {
affected_rows
returning {
id
task
completed
}
}
}
`;const MUTATION_TODO_UPDATE = gql`
mutation update_todo ($todoId: Int, $set: todo_input!) {
update_todo(where: {id: {_eq: $todoId}} _set: $set) {
affected_rows
}
}
`;const MUTATION_TODO_DELETE = gql`
mutation delete_todo ($todoId: Int) {
delete_todo(where: {id: {_eq: $todoId}}) {
affected_rows
}
}
`;export {
QUERY_TODO,
MUTATION_TODO_ADD,
MUTATION_TODO_UPDATE,
MUTATION_TODO_DELETE
};
Next we move on to our TodoComponents
TodoComponent.js
This component consists of two children components TodoInput
and TodoList
.
import React from 'react';
import '../../styles/Todo.css';
import TodoInput from './TodoInput';
import TodoList from './TodoList';export default class TodoComponent extends React.Component {render() {
return (
<div className="parentContainer">
<h1 className="header">Todos</h1>
<TodoInput userId={this.props.userInfo.hasura_id}/>
<TodoList />
</div>
)
}
}
To keep our code clean, let’s put all of our todo related components inside components/todo
.
TodoInput.js
We maintain the value of the input box in local state and then directly make the query to insert the query into the Hasura database and then update the local Apollo cache.
TodoList.js
Here, we are fetching the various todos for the particular user and then render each todo with the help of a Todo
component.
Todo.js
This component handles the updation(toggle) and deletion of todos.
Run the app
And there we have it! A todo app built on react with authentication and a database.
Deploying the app on the Hasura platform
Now that we are done building our app, let’s deploy this app on the Hasura platform.
The platform has a docker based deployment. We need to write a Dockerfile to run our app. But, to make our lives easier, the Hasura platform also provides a react quickstart with a microservice running a react app. We are going to clone that microservice and replace the apps code with ours.
To clone a microservice from another hub project
$ hasura microservice clone ui --from hasura/hello-react
To expose this microservice on a route
$ hasura conf generate-route ui >> conf/routes.yaml
To enable the ability to git push
to deploy this app
$ hasura conf generate-remote ui >> conf/ci.yaml
Your file structure will now look like this
The source code to the react app lives inside the microservices/ui/app
directory. Let’s go ahead and replace the contents inside app with the contents of our react app.
If you face problems running the app after moving it to another directory, delete your node_modules
and install it again by running npm install
After replacing the contents of app with our react code, the file structure would be
Deploying on the Hasura platform is literally pushing to a remote git repo
$ git add . && git commit -m "A good commit message"
$ git push hasura master
This will push the react app and make is accessible on the ui
subdomain.
To open the microservice on your web browser, run
$ hasura microservice open ui
To learn more about the Hasura platform check out the react boiler plate here.
You can find the code to this project at the following repo:
Hasura gives you instant GraphQL APIs over any Postgres database without having to write any backend code.