February 22, 2019

Tutorial: Building TodoMVC with 8base and GraphQL

Requirements

  • git
  • npm or yarn (we use {% code-line %}yarn{% code-line-end %} here, but you can use npm if you prefer)
  • Familiarity with React.js

Introduction

In this tutorial I’ll show you how to use 8base to quickly create a GraphQL API and connect to it from a React app. 8base is a Developer Acceleration Platform offering a plethora of features to help front-end developers build applications faster and simpler than ever before. With 8base, there is no need to rely on back-end developers or DevOps!

Using the 8base platform, you can build your backend using a simple GUI that allows you to perform actions such as:

  • Define your Schema: create tables/ table relationships
  • Upload files
  • Define roles
  • Manage various projects using “Workspaces”
  • Design queries using the API explorer (based on GraphQL)

I believe that the best way to illustrate the utility of 8base is to show interested users how it can be integrated with projects they are already working on. By connecting a simple Todo MVC app to an 8base backend, we’ll learn how to:

  • Navigate the 8base UI as well as create new workspaces and tables
  • Define role permissions
  • Write queries using GraphQL
  • Access your data via a React.js front-end using the 8base SDK

Intended Audience

This tutorial is aimed mostly toward users who are new to 8base and don’t have much prior experience with GraphQL. If you are familiar with GraphQL, connecting to your 8base backend should be a somewhat familiar process. Otherwise, everyone is encouraged to follow this tutorial to see 8base’s recommended implementation using the 8base SDK.

Preparing the Environment/ 8base UI

The developers at 8base created this platform with the intention of making something that they would want to use. As you navigate the 8base UI, you’ll notice that plenty of time and care were put into creating this platform to assure that the development experience would be pleasantly intuitive and that elements within the application are fluid and responsive in a modern way. In this tutorial, we will only use a few features offered by 8base, but I highly encourage you to explore the entirety of the platform and utilize other tools in the suite.

  1. Create an account on 8base (8base starts as a free to use platform and pricing changes as usage increases. See pricing details here. You will be asked to verify your email and then redirected to the 8base UI).
  2. Once you are in the 8base UI, simply navigate to “Data” and click on “New Table” to start building your backend. After your new table is loaded, you’ll be taken to the Schema in order to begin defining fields. Let’s take a look around and note a couple of things. On the left, you’ll see there are System Tables and Your Tables. Every new 8base workspace automatically comes prepackaged with a number of built-in Tables. These tables are used to handle things like Files, Settings, and Permissions and can all be accessed through the 8base GraphQL API. For now, the only system table that is exposed via the UI is Users, but you can find a complete list of 8base system tables by using the query tablesList in the API Explorer.
  3. Go ahead and create a table Todo with fields text (field type: TEXT), completed (field type: SWITCH, format: Yes/No). Switch is a particularly interesting field type. At first, it may seem like a boolean value, but the switch field is actually able to handle multiple options, and therefore allows for a lot of flexibility. However, for the sake of this project, we will simply be using Yes/No.
  4. Next, copy the API endpoint URL (available on the bottom left) — this is your main line of communication between your front end and your 8base backend (you’ll need it later where it says 8BASE_API_URL ).
  5. Finally, for the purpose of this tutorial we’re going to allow open access to Guests by default so that dealing with authentication is optional. To allow guest access to your new Todo table, navigate to Settings > Roles > Guest and check the appropriate boxes under Todo. All unauthenticated users who access your API endpoint are given the role of Guest by default. Authentication is not covered in this tutorial. You can see how authentication should be handled in more detail here.


Now that you’ve prepared your backend, let’s head over to the 8base TodoMVC repo to prepare our client application.

  • Clone the TodoMVC repo and run {% code-line %}git checkout workshop{% code-line-end %} to switch into the workshop branch.

The master branch contains the completed code for this project so you will be unable to follow along with the tutorial if you don’t complete this step.

  • Install dependencies:

{% code-block language="js" %}
yarn add @8base/app-provider graphql graphql-tag react-apollo && yarn
{% code-block-end %}

  • Test that the app works without backend: {% code-line %}yarn start{% code-line-end %}

  • (Optional) Setup VS Code Apollo GraphQL extension: Create the file apollo.config.js in the root of the project with the following content (replace 8BASE_API_URL with your API endpoint URL):

{% code-block language="js" %}
module.exports = {<br/>
    client: {
      service: {
        name: '8base',
        url: '8BASE_API_URL',
      },
      includes: [
        "src/*.{ts,tsx,js,jsx}"
      ]
    },
  };
{% code-block-end %}

The Apollo GraphQL extension for VS code provides validation and autocomplete for writing GraphQL queries.

Connecting the Backend

The application repo that we set up has the following code built in, so you can focus on understanding what’s going on. In instances where you have to add code, just uncomment what is specified. In instances where the code has to be removed, the specified code is appended by the comment "//Remove this" in the code base.

Note that all relevant code in this tutorial will be placed in the file src/App.js — this is not best practice using React but has been done this way to simplify the tutorial.

  1. Import GraphQL-related dependencies

{% code-block language="js" %}
import gql from "graphql-tag";
import { graphql } from "react-apollo";
import { EightBaseAppProvider } from '@8base/app-provider';
{% code-block-end %}

You’ll see here not only are we importing gql and graphql, but we are also importing EightBaseAppProvider from the 8base SDK. The 8base SDK makes integration with 8base and GraphQL simple. We've done this by taking a lot of the boilerplate/setup-code and packaging it inside the SDK, so all the developer needs to do is wrap their application in the {% code-line %}<EightBaseAppProvider>{% code-line-end %} tag and pass it the appropriate props to allow data access to all the child components. EightBaseAppProvider uses Apollo Client, so if you are familiar with it already, there is nothing new to learn.

2. Initialize EightBaseAppProvider

{% code-block language="js" %}
<EightBaseAppProvider uri={ENDPOINT_URL} >
{({ loading }) => loading ? <div>"Loading..."</div> : (
  <div className="todoapp">...</div> })}
</EightBaseAppProvider>
{% code-block-end %}

You need to provide a function as a child of EightBaseAppProvider that will tell React what we want to render. We can use the loading property to display a loader while the application is being initialized. During the initialization, EightBaseAppProvider loads the 8base table schema which gives you access to all properties of the data model inside your front end code. We'll touch on this feature more in-depth in a future tutorial.

3. Add a Query to Fetch todos

{% code-block language="js" %}
const TODO_LIST_QUERY = gql`
query TodoList {
  todosList(orderBy: [completed_ASC, createdAt_DESC]) {
    items {
      id
      text
      completed
    }
  }
}
`;

const withTodos = graphql(TODO_LIST_QUERY, {
props: ({ data: { todosList: ({ items } = {}) } }) => {
  return {
    todos: items || []
  };
},
});
{% code-block-end %}

In the code above, we are creating a Higher-Order Component using the graphql function provided by react-apollo.

From react-apollo — The graphql() function is the most important thing exported by react-apollo. With this function you can create higher-order components that can execute queries and update reactively based on the data in your Apollo store. The graphql() function returns a function which will “enhance” any component with reactive GraphQL capabilities. This follows the React higher-order component pattern which is also used by react-redux’s connect function.

Essentially, by using graphql we are able to write the code to query our backend in one place and inject this functionality into multiple components rather than writing it everywhere we want to be able to use it. The graphql function takes a query as its first parameter, config as the second, and the return value is a HOC that should be executed with the desired component as an argument. Note that in our example, we use the config parameter in graphql to specify that our data will be accessible as {%code-line%}props.todos{%code-line-end%} rather than {%code-line%}props.data.todosList.items{%code-line-end%}. As for our TODO_LIST_QUERY, we've already given you the appropriate syntax, but typically we might design our queries in the 8base API Explorer. If you navigate to the API Explorer and copy/paste the TODO_LIST_QUERY, you can see what data will be returned. Experiment with this query and see other ways that data can be returned.

  • Wrap Main and Footer into withTodos

{% code-block language="js" %}
Main = compose(
withRouter,
withTodos /* Add this */
)(Main);
{% code-block-end %}

{% code-block language="js" %}
Footer = compose(
withRouter,
withTodos /* Add this */
)(Footer);
{% code-block-end %}

Ultimately in our example, we want to display all the data from our 8base backend in the Main component and the Footer component. In the code above we are using the compose function provided by the lodash library to chain multiple Higher-Order Components together, giving our target components the functionality from each HOC. You can read more about how the compose function works here.

  • Remove the code that passes down the todos prop to the Main and Footer components

{% code-block language="js" %}
<Main
  todos={ this.state.todos }  /* Remove this */

<Footer
  todos={ this.state.todos }  /* Remove this */
{% code-block-end %}

When we first set up our application, all Todos were held in state because we were not connected to a backend. Our components still need to access the todos props, but we no longer have to pass it down explicitly because they are provided by withTodos.

4. Add a Query to Create a todo

{% code-block language="js" %}
const CREATE_TODO_MUTATION = gql`
mutation TodoCreate($data: TodoCreateInput!) {
  todoCreate(data: $data) {
    id
    text
    completed
  }
}
`;

const withCreateTodo = graphql(CREATE_TODO_MUTATION, {
props: ({ mutate }) => ({
  createTodo: ({ text }) => {
    mutate({
      variables: { data: { text, completed: false } },
      refetchQueries: [{ query: TODO_LIST_QUERY }]
    });
  }
})
});
{% code-block-end %}

Here we are repeating the process we took previously to query our 8base backend, only this time we are using GraphQL mutation to create a Todo and then calling refetchQueries to update the state of our application with our newly added data. This will allow new data to populate throughout the application without having to make a separate request to our backend.

  • Wrap Header

{% code-block language="js" %}
Header = withCreateTodo(Header);
{% code-block-end %}

The syntax above illustrates the enhancement of the Header component using our HOC. Note that withCreateTodo acts as a function, the WORDS_HIGHLIGHT:1:Header component is passed to the function as an argument and the enhanced component is then set to the variable Header to be used in our application.

  • Remove createTodo from Header

{% code-block language="js" %}
<Header
  createTodo={ this.createTodo }  /* Remove this */
{% code-block-end %}

5. Add a Query to update todos

  • Mutation and HOC

{% code-block language="js" %}
const TOGGLE_TODO_MUTATION = gql`
mutation TodoToggle($id: ID!, $completed: Boolean!) {
  todoUpdate(filter: { id: $id }, data: {
      completed: $completed
  }) {
    id
    text
    completed
  }
}
`;

const withToggleTodo = graphql(TOGGLE_TODO_MUTATION, {
props: ({ mutate }) => ({
  toggleTodo: ({ id, completed }) => {
    mutate({
      variables: { id, completed },
      refetchQueries: [{ query: TODO_LIST_QUERY }]
    });
  }
})  
});
{% code-block-end %}

  • Wrap Main in withToggleTodo

{% code-block language="js" %}
Main = compose(
withRouter,
withTodos,
withToggleTodo  /* Add this */
)(Main);
{% code-block-end %}

  • Remove toggleTodo from Main

{% code-block language="js" %}
<Main
  toggleTodo={ this.toggleTodo }  /* Remove this */
  ...
{% code-block-end %}

Above we’ve repeated the previous pattern to add functionality to the Main component. This will give the user the ability to toggle Todos as complete or incomplete.

6. Add a Query to mark all todos as complete

  • We only need a new HOC, we can reuse the mutation. All mutations in the loop will be batched in a single request.

{% code-block language="js" %}
const withToggleAllTodos = graphql(TOGGLE_TODO_MUTATION, {
props: ({ mutate, ownProps: { todos }}) => ({
  toggleAllTodos: ({ completed }) => {      
    todos.forEach((todo) => {
      mutate({
        variables: { id: todo.id, completed },
        refetchQueries: [{ query: TODO_LIST_QUERY }]
      });        
    });      
  }
})
});
{% code-block-end %}

  • Wrap Main in withToggleAllTodos

{% code-block language="js" %}
Main = compose(
withRouter,
withTodos,
withToggleTodo,
withToggleAllTodos /* Add this */
)(Main);
{% code-block-end %}

  • Remove toggleAllTodos from Main

{% code-block language="js" %}
<Main
  toggleAllTodos={ this.toggleAllTodos }  /* Remove this */
  ...
{% code-block-end %}

7. Add a Query to delete todos

  • Mutation and HOC

{% code-block language="js" %}
<Main
 const DELETE_TODO_MUTATION = gql`
 mutation TodoDelete($id: ID!) {
  todoDelete(filter: { id: $id }) {
    success
  }
}
`;

const withRemoveTodo = graphql(DELETE_TODO_MUTATION, {
props: ({ mutate }) => ({
  removeTodo: ( id ) => {
    mutate({
      variables: { id },
      refetchQueries: [{ query: TODO_LIST_QUERY }]
    });
  }
})  
});
{% code-block-end %}

  • Wrap Main in withRemoveTodo

{% code-block language="js" %}
Main = compose(
withRouter,
withTodos,
withToggleTodo,
withRemoveTodo /* Add this */
)(Main);
{% code-block-end %}

  • Remove removeTodo from Main

{% code-block language="js" %}
<Main
  removeTodo={ this.removeTodo }  /* Remove this */
  ...
{% code-block-end %}

As you can see, once you understand the basic pattern of enhancing components using the graphql HOC, it becomes fairly simple to write new queries that can be easily accessed throughout your application.

Finally, test that your application works properly by navigating to the root directory and running {% code-line %}yarn start{% code-line-end %} .

You should now be able to create, update and destroy Todos that are persisted to the database.

  • To see 8base working in action, open up your 8base workspace in a separate tab and open the Data Viewer on the Todos table. You should see all the data from your application in this window. Create new Todos in your application and refresh the page to see them in your database!

8base is currently working on implementations using other frameworks and libraries. We currently have examples using React and React-Native as well as more simple examples using CURL, Node, and Vanilla JS. Feel free to visit our Docs, send a message on our website, or reach out to me personally with feedback on your experience with 8base.

Completed example with 8base backend connected can be found in the master branch.

Ready to try 8base?

Sign up for free or simply stay in touch.