Client App

At this point we have all the infrastructure (database table, permissions and an API) needed to store and retrieve todos from any client application. It is now time to start building one!

React app

We are going to use create-react-app because it is the easiest way to get started and it offers a modern build setup with no configuration. In your terminal, please type in the following:

npx create-react-app nhost-todos

After create-react-app finishes, we will install some packages that will make our lifes easier when connecting to the Nhost backend.

cd nhost-todos

npm install react-nhost @apollo/client graphql graphql-tag react-router-dom

Now it's a good time to start the app and see that everything is working as expected.

npm start

You should see the usual React logo.

Before we move on to the next section, one last thing we need to do is to create a jsconfig.json file at the root directory to make imports easier.

jsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

Let's go!

Add NhostApolloProvider to index.js

NhostApolloProvider comes from react-nhost and deals with all the boilerplate for having ApolloClient properly configured to communicate securely with Nhost.

Import NhostApolloProvider and wrap App so that GraphQL is available everywhere.

Replace https://hasura-xxx.nhost.app/v1/graphql with your project's GraphQL API endpoint. You can find the endpoint in your project's dashboard.

src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "App";
import { NhostApolloProvider } from "react-nhost";
ReactDOM.render( <React.StrictMode>
<NhostApolloProvider gqlEndpoint="https://hasura-xxx.nhost.app/v1/graphql">
<App />
</NhostApolloProvider>
</React.StrictMode>, document.getElementById("root") );

Now, with ApolloClient configured, we can move on to fetch data.

Fetch data using GraphQL in App.js

Replace all content of src/App.js with the following code:

src/App.js
import React from "react";
import { useQuery } from "@apollo/client";
import gql from "graphql-tag";

const GET_TODOS = gql`
  query {
    todos {
      id
      created_at
      name
      completed
    }
  }
`;

function App() {
  const { data, loading } = useQuery(GET_TODOS);

  if (loading) {
    return <div>Loading</div>;
  }

  return (
    <div>
      {!data ? (
        "No todos"
      ) : (
        <ul>
          {data.todos.map((todo) => {
            return <li key={todo.id}>{todo.name}</li>;
          })}
        </ul>
      )}
    </div>
  );
}

export default App;

Go back to where your app is running and make sure that the todos you created earlier are being listed there.

Let's move on and add functionality to create new todos.

Add todos

We need to create a GraphQL mutation and add a simple form for creating todos.

src/App.js
import React, { useState } from "react";
import { useQuery, useMutation } from "@apollo/client";
import gql from "graphql-tag"; const GET_TODOS = gql` query { todos { id created_at name completed } } `;
const INSERT_TODO = gql` mutation($todo: todos_insert_input!) { insert_todos(objects: [$todo]) { affected_rows } } `;
function App() { const { data, loading } = useQuery(GET_TODOS);
const [insertTodo] = useMutation(INSERT_TODO);
const [todoName, setTodoName] = useState("");
if (loading) { return <div>Loading</div>; } return ( <div>
<div>
<form
onSubmit={async (e) => {
e.preventDefault();
try {
await insertTodo({
variables: {
todo: {
name: todoName,
},
},
});
} catch (error) {
alert("Error creating todo");
console.log(error);
}
alert("Todo created");
setTodoName("");
}}
>
<input type="text" placeholder="todo" value={todoName} onChange={(e) => setTodoName(e.target.value)} />
<button>Create todo</button>
</form>
</div>
{!data ? ( "no data" ) : ( <ul> {data.todos.map((todo) => { return <li key={todo.id}>{todo.name}</li>; })} </ul> )} </div> ); } export default App;

Great! we can now create new todos using the form we just added. But for the new todos to be listed, we have to reload the page ourselves. We can do better, so let's fix that by introducing subscriptions.

Get data in realtime using subscriptions

Subscriptions are used to get data in realtime. When the data changes in the database our app will instantly be updated and show the newest data. It's almost like magic!

We'll replace useQuery with useSubscription and query with subscription in GET_TODOS.

src/App.js
import React, { useState } from "react";
import { useSubscription, useMutation } from "@apollo/client";
import gql from "graphql-tag";
const GET_TODOS = gql` subscription { todos { id created_at name completed } } `;
const INSERT_TODO = gql` mutation($todo: todos_insert_input!) { insert_todos(objects: [$todo]) { affected_rows } } `; function App() {
const { data, loading } = useSubscription(GET_TODOS);
const [insertTodo] = useMutation(INSERT_TODO); const [todoName, setTodoName] = useState(""); if (loading) { return <div>Loading</div>; } return ( <div> <div> <form onSubmit={async (e) => { e.preventDefault(); try { await insertTodo({ variables: { todo: { name: todoName, }, }, }); } catch (error) { alert("Error creating todo"); console.log(error); } alert("Todo created"); setTodoName(""); }} > <input type="text" placeholder="todo" value={todoName} onChange={(e) => setTodoName(e.target.value)} /> <button>Add todo</button> </form> </div> {!data ? ( "no data" ) : ( <ul> {data.todos.map((todo) => { return <li key={todo.id}>{todo.name}</li>; })} </ul> )} </div> ); } export default App;

Perfect! New todos get listed in realtime when we create them. You can even open two tabs, add new todos in one tab and see how all windows display the latest data.

Your app now has superpowers!

Next

Authentication