DEV Community

Cover image for How to create a React Native app with PostgreSQL and GraphQL: Part 1
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

How to create a React Native app with PostgreSQL and GraphQL: Part 1

Written by Austin Roy Omondi✏️

React Native is a great option when it comes to developing mobile applications for both iOS and Android smartphones. With React Native you can write an application that works on both platforms with the only difference coming at the view level, with iOS and Android rendering differently. In this two-part series, we’ll be looking at how we can develop an application with React Native and GraphQL. To do this, we’ll be building a simple note-taking app that allows our user to add notes, view, edit and delete them.

For the first part, we’ll be building a GraphQL server with a PostgreSQL database that will serve as the backend of our application. In the second part (blog post to come later) we will build an application with React Native that will consume data from our server.

Let’s get started with our server setup. The folder structure for our project will be as shown in this repo.

In order to build our application, you will need to be familiar with:

  • NodeJS
  • Express
  • Databases
  • Mobile development

LogRocket Free Trial Banner

How to set up an Apollo server with Express

Our backend server will run on Node JS and Express. We’ll have a few initial dependencies to install, but first, we’ll need to initiate our app, run:

npm init
Enter fullscreen mode Exit fullscreen mode

Now let us install our first set of dependencies:

npm install apollo-server apollo-server-express express graphql cors --save
Enter fullscreen mode Exit fullscreen mode

One of the dependencies we have installed is apollo-server, Apollo Server is the best way to quickly build a production-ready, self-documenting API for GraphQL clients, using data from any source. While we are using it with Express, it may be used with several popular libraries such as Hapi and Koa.

Now we are all set to start creating our server. Add the following code to src/index.js.

import cors from 'cors';
import express from 'express';
import {
    ApolloServer
} from 'apollo-server-express';

const app = express();

app.use(cors());

const server = new ApolloServer({});

server.applyMiddleware({
    app,
    path: '/graphql'
});

app.listen({
    port: 8000
}, () => {
    console.log('Apollo Server on http://localhost:8000/graphql');
});
Enter fullscreen mode Exit fullscreen mode

Let’s go through what we have so far, in the code above, we have initialized an Express application as well as an Apollo server instance. Using Apollo Server’s applyMiddleware() method, you can opt-in any middleware, which in our case is the Express app. Also, you can specify the path for your GraphQL API endpoint.

You may also note we applied cors to our Express application, this is to allow external domains to access our application.

Our next step would be to define our schema and resolvers for the Apollo Server instance. But first, let’s cover schema and resolvers.

Schema

The GraphQL schema provided to the Apollo Server is all of the available data for reading and writing data via GraphQL. The schema contains type definitions from a mandatory top-level Query type that allows reading of data followed by field and nested fields. These are defined using the GraphQL Schema Language and allows us to talk about GraphQL schemas in a language-agnostic way.

Here’s what’s in our schema/notes.js:

import {
  gql
} from 'apollo-server-express';

export default gql `
  extend type Query {
    notes: [Note!]
    note(id: ID!): Note!
  }

  type Note {
    id: ID!
    text: String!
  }
`;
Enter fullscreen mode Exit fullscreen mode

Let’s take a look at our schema definition for some notable terms we need to be aware of. We have already covered the Query type, but let’s look at the rest:

  • notes is defined as [Note!] which represents an array of Note objects. The exclamation mark means it is non-nullable, this means you can always expect an array (with zero or more items) when you query the notes field
  • Each Note object contains an id field defined as ID . The ID scalar type represents a unique identifier, often used to re-fetch an object or as the key for a cache. It is serialized the same way a String is serialized. However, defining it as an ID signifies that it is not intended to be human‐readable
  • The Note object also has a text field defined as String which is one of the built-in scalar types that is a sequence of UTF-8 characters
  • You will also notice the note field is provided with id options in the form of (id: ID!). This allows for the use of GraphQL arguments to refine queries. This allows us to fetch a single note by providing it’s ID in our query. The argument is also non-nullable as it must be provided when fetching a single note.

The schema above shall be placed in src/schema/notes.js. We can then have a index.js file inside the src/schema directory with the following code:

import { gql } from "apollo-server-express";

    import noteSchema from "./notes";

    const linkSchema = gql`
      type Query {
        _: Boolean
      }

      type Mutation {
        _: Boolean
      }

      type Subscription {
        _: Boolean
      }
    `;

    export default [linkSchema, noteSchema];
Enter fullscreen mode Exit fullscreen mode

Resolvers

Resolvers are responsible for manipulating and returning data, think of them as the query handlers. Each top-level query in the Query type has a resolver but we’ll make our own per field. The resolvers are grouped in a JavaScript object, often called a resolver map.

Let’s create a notes.js file inside src/resolvers and add the following code to it:

export default {
    Query: {
        notes: (parent, args, {
            models
        }) => {
            return Object.values(models.notes)
        },
        note: (parent, {
            id
        }, {
            models
        }) => {
            return models.notes[id]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The above resolvers allow us to query either individual notes using the note field and provide an id argument or an array of notes using the notes field. We’ll clean up our resolvers once we link our app to the database using sequelize and add Mutations which will allow us to carry out operations other than reading on our data. For now, these resolve dummy data that we’ll have in src/models/index.js which contains the following code:

let notes = {
    1: {
        id: '1',
        text: 'Hello World',
    },
    2: {
        id: '2',
        text: 'By World',
    },
};

export default {
    notes
};
Enter fullscreen mode Exit fullscreen mode

Now that we have the framework set up for how our data will be stored let’s look at how data manipulation will be done using queries and mutations.

Queries and mutations

Queries and mutations in GraphQL allow us to access and manipulate data on a GraphQL server. Queries are in charge of read operations whereas mutations are in charge of create, update and delete operations.

To create our queries and mutations, we must first define them in our schema. Let’s edit src/schema/notes.js as follows:

import {
  gql
} from 'apollo-server-express';

export default gql `
  extend type Query {
    notes: [Note!]
    note(id: ID!): Note!
  }

  extend type Mutation {
    createNewNote(text: String!): Note!
    deleteNote(id: ID!): Boolean!
    updateNote(id: ID!, text: String!): Note!
  }

  type Note {
    id: ID!
    text: String!
  }
`;
Enter fullscreen mode Exit fullscreen mode

Queries have already been defined earlier so our new addition is the Mutation type. From the types defined, you can get an idea of what the returned data will look like. For instance, createNewNote takes a text argument that is a string and generates and returns a Note object, updateNote is similar but it also takes the id so that it can find the note to be updated. Meanwhile, deleteNote takes an id boolean that is used to identify the target note and returns a boolean indicating whether the note has been deleted successfully or not.

This is how you can test your queries. Type the following code in your graphql playground:

Get all notes

Fetches and returns all notes saved.

query {
  notes {
    id
    text
  }
}
Enter fullscreen mode Exit fullscreen mode

Next hit the Execute Query button and you should be able to see the following result:

Result

{
  "data": {
    "notes": [
      {
        "id": "1",
        "text": "Hello World"
      },
      {
        "id": "3",
        "text": "By World"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Get note by id

This returns a piece of the data matching the given argument id

query{
  note(id: "1") {
    id
   text
  }
}
Enter fullscreen mode Exit fullscreen mode

Result

{
  "data": {
    "note": {
      "id": "1",
      "text": "Hello World"
    }
  }
Enter fullscreen mode Exit fullscreen mode

Next, we’ll need to write a Mutation resolver. Let’s editsrc/resolvers/notes.js

like this:

import uuidv4 from 'uuid/v4';

export default {
    Query: {
        notes: (parent, args, {
            models
        }) => {
            return Object.values(models.notes)
        },
        note: (parent, {
            id
        }, {
            models
        }) => {
            return models.notes[id]
        }
    },
    Mutation: {
        createNewNote: (parent, {
            text
        }, {
            models
        }) => {
            const id = uuidv4();
            const newNote = {
                id,
                text
            }
            models.notes[id] = newNote;
            return newNote;
        },

        deleteNote: (parent, {
            id
        }, {
            models
        }) => {
            const {
                [id]: note, ...otherNotes
            } = models.notes
            if (!note) {
                return false
            }
            models.notes = otherNotes
            return true
        },
}
Enter fullscreen mode Exit fullscreen mode

For the createNewNote Mutation we are generating the unique id using the uuid/v4 library so we’ll need to install it by running:

npm install uuid/v4 --save

The resolver takes a text argument, generates a unique id and uses them to create a Note object which is then appended to the list of notes. It then returns the new note that has been created.

The deleteNote Mutation takes an id argument, finds a note and creates a new notes object without the note to be deleted and replaces the old notes object with the new one. It returns false if the note is not found and true when the note is found and deleted.

The updateNote Mutation takes the id and text as arguments and updates the Note in the notes object accordingly, returning the updated note at the end.

We can now access these endpoints through the GraphQL playground when we run our application by running npm start and going to localhost:3000/graphql on the browser. You can then try out these actions:

Create a new note

Takes a text argument and creates and returns a note.

mutation {
  createNewNote( text: "Hello GraphQl") {
    id
    text
  }
}
Enter fullscreen mode Exit fullscreen mode

Result

{
  "data": {
    "createNewNote": {
      "id": "3",
      "text": "Hello GraphQl"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Update note

Takes text and id arguments and updates and returns a note.

mutation {
  updateNote(id:3, text:"Graphql is awesome") {
    id
    text
  }
}
Enter fullscreen mode Exit fullscreen mode

Result

{
  "data": {
    "updateNote": {
      "id": "4",
      "text": "Graphql is awesome"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Delete note

Takes an id argument and deletes the matching note.

mutation {
  deleteNote(id:5)
}
Enter fullscreen mode Exit fullscreen mode

Adding PostgreSQL with Sequelize

We have most of our app ready to go so the next thing we need to look at is how we can save our data to a database. Our database will be PostgreSQL so go ahead and install it from their site then start it.

After that, create a database called mynotes. To do this you can run the command psql in your terminal to open up the PosgreSQL CLI. Once open run:

CREATE DATABASE mynotes;
CREATE USER postgres;
GRANT ALL PRIVILEGES ON DATABASE members TO postgres;
Enter fullscreen mode Exit fullscreen mode

Next you’ll want to add PostgreSQL for Node.js and Sequelize (ORM) to your project. Run:

npm install pg sequelize --save
Enter fullscreen mode Exit fullscreen mode

The next step is to update our models to prepare to link to the database as shown below. The model defines the shape of each piece of data as it will be stored in the database:

const note = (sequelize, DataTypes) => {
  const Note = sequelize.define("note", {
    text: DataTypes.STRING
  });
  return Note;
};

export default note;
Enter fullscreen mode Exit fullscreen mode

Next, connect to your database from within your application in the src/models/index.js file:

import Sequelize from "sequelize";

const sequelize = new Sequelize(
  process.env.DATABASE,
  process.env.DATABASE_USER,
  process.env.DATABASE_PASSWORD,
  {
    dialect: "postgres"
  }
);

const models = {
  Note: sequelize.import("./notes")
};

Object.keys(models).forEach((key) => {
  if ("associate" in models[key]) {
    models[key].associate(models);
  }
});

export { sequelize };
export default models;
Enter fullscreen mode Exit fullscreen mode

You’ll need to define the database name, a database super-user, and the user’s password. You may also want to define a database dialect because Sequelize supports other databases as well.

In the same file, you can physically associate all your models with each other if you have multiple models (hence the foreEach loop above) to expose them to your application as data access layer (models) for the database. In our case, we only have one model but this is still good to know.

Now let’s create a .env file at the root directory of the project to hold some critical data as environment variables. The database credentials (database name, database super user name, database super-user password) can be stored as environment variables along with the port on which we wish to run our server.

In the .env file add the following variables or change them to your liking:

PORT=3000
DATABASE=mynotes
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres
Enter fullscreen mode Exit fullscreen mode

Next, let’s update src/index.js to get our databases synchronized when our application is started. Make the following changes:

...
import 'dotenv/config';
import models, {
    sequelize
} from './models';
...

sequelize.sync().then(async () => {
    app.listen({
        port: 8000 // you could change this to process.env.PORT if you wish
    }, () => {
        console.log('Apollo Server on http://localhost:8000/graphql');
    });
});
Enter fullscreen mode Exit fullscreen mode

We’ve now linked our application to the database so what we need to do next is replace the logic in our resolvers.

How to connect resolvers to the database

We had dummy data before but with Sequelize we can now allow us to sync our data with our database, allowing for data persistence. To make this happen, we need to update our resolver in src/resolvers/notes.js to integrate the Sequelize API.

export default {
  Query: {
    notes: async (parent, args, { models }) => {
      return await models.Note.findAll();
    },

    note: async (parent, { id }, { models }) => {
      return await models.Note.findByPk(id);
    }
  },
  Mutation: {
    createNewNote: async (parent, { text }, { models }) => {
      return await models.Note.create({
        text
      });
    },

    deleteNote: async (parent, { id }, { models }) => {
      return await models.Note.destroy({
        where: {
          id
        }
      });
    },
    updateNote: async (parent, { id, text }, { models }) => {
      await models.Note.update(
        {
          text
        },
        {
          where: {
            id: id
          }
        }
      );
      const updatedNote = await models.Note.findByPk(id, {
        include
      });
      return updatedNote;
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

You will notice we no longer need uuid/v4 as Sequelize automatically handles the assignment of ids in our database. The findAll() and findByPk() are commonly used Sequelize methods for database operations, findAll() returns all entries in a certain table whereas findPk() identifies and returns entries that fit a certain criterion such as a matching id in our case.

Other Sequelize methods we make use of include update() and destroy() to update and delete records respectively.

Along with these, we use async/await to implement promise-based asynchronous Javascript requests to our database.

Conclusion

Great! We are all ready to go, the next step is to integrate our GraphQL backend with our front-end app, which we’ll build for mobile in part two of this tutorial. We’ll be working with Apollo and React Native which should be exciting. Stay tuned!


Editor's note: Seeing something wrong with this post? You can find the correct version here.

Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
Try it for free.


The post How to create a React Native app with PostgreSQL and GraphQL: Part 1 appeared first on LogRocket Blog.

Oldest comments (0)