Frontend Web App

At this point, we have all the backend (database table, permissions, and an API) needed to store and retrieve todos from any frontend app. It is now time to start building the actual frontend app!

#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.

Create a new react app:

$ npx create-react-app nhost-todos

Once completed, change the directory to the new project and install these packages. The packages will be used to send GraphQL requests to the Nhost backend.

$ cd nhost-todos
$ npm install @nhost/react-apollo@v1.1.3 @apollo/client graphql react-router-dom

Create a jsconfig.json file in the root of your project to make imports easier.

jsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

Start the app.

$ npm start

#Add NhostApolloProvider to index.js

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

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

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 "@nhost/react-apollo";
ReactDOM.render( <React.StrictMode>
<NhostApolloProvider gqlEndpoint="https://hasura-xxx.nhost.app/v1/graphql">
<App />
</NhostApolloProvider>
</React.StrictMode>, document.getElementById("root") );

Now we can 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 { gql, useQuery } from "@apollo/client";

const GET_TODOS = gql`
  query {
    todos {
      id
      created_at
      name
      is_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;

Your two previous todos now appear in your app.

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

#Insert todos

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

src/App.js
import React, { useState } from "react";
import { gql, useQuery, useMutation } from "@apollo/client";
const GET_TODOS = gql` query { todos { id created_at name is_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) {
console.error(error);
return alert("Error creating todo");
}
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 insert new todos. 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.

Subscriptions updates data in realtime. When the data changes in the database, our app instantly updates and shows the newest data. It's almost like magic!

#Get data in realtime using subscriptions

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

src/App.js
import React, { useState } from "react";
import { gql, useSubscription, useMutation } from "@apollo/client";
const GET_TODOS = gql` subscription { todos { id created_at name is_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.error(error); return; } 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 has superpowers!

#Next

Authentication