Build a CRUD SPA with Vuejs, Apollo Client and GraphQL 

4184 VIEWS

· · ·

Introduction

This is a tutorial for building a CRUD SPA using Vue.js and GraphQL. It is for any budding frontend developer who would like to connect their Vue.js SPA user interface by consuming GraphQL API backend for an end-to-end Create, Read, Update and Delete (CRUD) Functionality. Through the tutorial, they will be able to learn how to use Apollo client and integrate it with Vue.js to send data requests and make changes to an application.

Vue (pronounced /vjuː/, like view) is a progressive JavaScript framework for building user interfaces.  It is built incrementally from the ground up, and its core library focuses on the view layer thus ensuring easy integration with existing projects and libraries. It is mostly known for its popularity in building single-page applications (SPAs) when combined with other modern backend tools such as GraphQL API server.

GraphQL is a runtime and a query language for building web APIs. GraphQL provides a simplified description of data where clients can make a single data request to fetch data that would have otherwise taken multiple REST endpoints, hence increasing performance.

Prerequisites

To follow this tutorial, you need to have:

  • NPM and Node installed on your development machine. Follow the official documentation for installation guidance.
  • Basic knowledge in JavaScript and Vue.

If you have these prerequisites, you are good to go!

This tutorial is divided into two parts: 

  1. The backend- building a GraphQL API with Node.js and Express. The API data sits on the GraphQL server, and defines the application functionality.
  2. The frontend- building a Vue.js Single Page Application User Interface where the Create, Read, Update, and Deletion of data is done. The application will then consume the GraphQL API data set up in part 1.

Part 1: Building a GraphQL API with Node.js and Express

Step 1: Setting up the project

Open a new command line prompt and run the below commands to install the default package.json file.

mkdir node-graphql-demo

cd node-graphql-demo

npm init -y 
Step 2: Install Express Framework

The express framework serves as the key dependency for GraphQL implementation for Node.js, which is the middleware sitting between Express and SQLite3 database. We are using SQLite for this tutorial. You can apply other database tools as well.  If you are familiar with MongoDB, feel free to follow this SweetCode tutorial for setting up GraphQL apollo client with MongoDB.

//npm install graphql express express-graphql sqlite3 –save 
Step 3: Create the GraphQL API Server

With all the required project dependencies installed, it is time to create the  API server. Open the project folder in your preferred development environment such as Visual Studio Code. At the root of your project directory, create an index.js file. Start by importing Express, SQLite3, GraphQL, and Express GraphQL middleware, by adding them at the top of the file.

/*Import Express, SQLite3, GraphQL, and Express GraphQL middleware */

const  express  =  require('express');

const  sqlite3  =  require('sqlite3').verbose();

const ExpressGraphQL = require("express-graphql");

const graphql = require("graphql");

Next, create an instance of the Express app as well as the SQLite3 database within the current project folder.

/*Create an express app */

const  app  =  express();

 

 /*Create an SQLite3 database schema, my.db within the current folder*/

const database = new sqlite3.Database("./my.db");

In the next step, you will create an SQL table within the database. Contacts will be stored here. Each contact object is attributed by their id, first name last name and email and will be stored as a row in the entity. For every contact attribute, a GraphQL type is defined, for instance, first name is a GraphQL string.

/*Create a Contacts Table within the Schema */

const  createContactTable  = () => {

    const  query  =  `

        CREATE TABLE IF NOT EXISTS contacts (

        id integer PRIMARY KEY,

        firstName text,

        lastName text,

        email text UNIQUE)`;

 

    return  database.run(query);

}

 

createContactTable();

 

/* Define the attribute GraphQL types */

const ContactType = new graphql.GraphQLObjectType({

    name: "Contact",

    fields: {

        id: { type: graphql.GraphQLID },

        firstName: { type: graphql.GraphQLString },

        lastName: { type: graphql.GraphQLString },

        email: { type: graphql.GraphQLString }   

    }

});
Step 4: Define the query type

There are two parts that make up every database: a schema that defines the data model of the GraphQL server, and the actual query specification for fetching the data. In this step, you get to define the query in two parts, contacts which is used to fetch all the contacts records in the database and contact which fetches a single record of contact based on its id. To fetch data, you need resolvers, GraphQL functions used to return the actual underlying value of a field. A resolver also specifies the method that defines and executes the logic of fetching data.

/* Define the GraphQL Query Type */

var queryType = new graphql.GraphQLObjectType({

    name: 'Query',

    fields: {

        contacts: {

            type: graphql.GraphQLList(ContactType),

            resolve: (root, args, context, info) => {

                return new Promise((resolve, reject) => {

                    

                    database.all("SELECT * FROM contacts;", function(err, rows) {  

                        if(err){

                            reject([]);

                        }

                        resolve(rows);

                    });

                });

                

                

            }

        },

        contact:{

            type: ContactType,

            args:{

                id:{

                    type: new graphql.GraphQLNonNull(graphql.GraphQLID)

                }               

            },

            resolve: (root, {id}, context, info) => {

                return new Promise((resolve, reject) => {

                

                    database.all("SELECT * FROM contacts WHERE id = (?);",[id], function(err, rows) {                           

                        if(err){

                            reject(null);

                        }

                        resolve(rows[0]);

                    });

                });

            }

        }

    }

});

After defining the query type, create a mutation type for creating, updating, and deleting contact records. These are the method operations for the CRUD functionality of the application. Every mutation type method accepts args property that are passed as arguments within the resolve() function which executes the corresponding SQL operation to return the desired Promise.

/*Create mutation methods for quering the data */

var mutationType = new graphql.GraphQLObjectType({

    name: 'Mutation',

    fields: {

      createContact: {

        type: ContactType,

        args: {

          firstName: {

            type: new graphql.GraphQLNonNull(graphql.GraphQLString)

          },

          lastName:{

              type: new graphql.GraphQLNonNull(graphql.GraphQLString)

          },

          email: {

              type: new graphql.GraphQLNonNull(graphql.GraphQLString)

          } 

        },

        resolve: (root, {firstName , lastName, email}) => {

            return new Promise((resolve, reject) => {

 

                database.run('INSERT INTO contacts (firstName, lastName, email) VALUES (?,?,?);', [firstName , lastName, email], (err) => {

                    if(err) {

                        reject(null);

                    }

                    database.get("SELECT last_insert_rowid() as id", (err, row) => {

                        

                        resolve({

                            id: row["id"],

                            firstName: firstName,

                            lastName: lastName,

                            email: email

                        });

                    });

                });

            })

 

        }

      },

      updateContact: {

        type: graphql.GraphQLString,

        args:{

            id:{

                type: new graphql.GraphQLNonNull(graphql.GraphQLID)

            },

            firstName: {

                type: new graphql.GraphQLNonNull(graphql.GraphQLString)

            },

            lastName:{

                  type: new graphql.GraphQLNonNull(graphql.GraphQLString)

            },

            email: {

                  type: new graphql.GraphQLNonNull(graphql.GraphQLString)

            }                

        },

        resolve: (root, {id, firstName , lastName, email}) => {

            return new Promise((resolve, reject) => {

 

                database.run('UPDATE contacts SET firstName = (?), lastName = (?), email = (?) WHERE id = (?);', [firstName, lastName, email, id], (err) => {

                    if(err) {

                        reject(err);

                    }

                    resolve(`Contact #${id} updated`);

                    

                });

            })

        }

      },

      deleteContact: {

        type: graphql.GraphQLString,

        args:{

            id:{

                type: new graphql.GraphQLNonNull(graphql.GraphQLID)

            }               

        },

        resolve: (root, {id}) => {

            return new Promise((resolve, reject) => {

 

                database.run('DELETE from contacts WHERE id =(?);', [id], (err) => {

                    if(err) {

                        reject(err);

                    }

                    resolve(`Contact #${id} deleted`);

                    

                });

            })

 

        }

      }

    }

});

Step 5: Create an instance of the GraphQL schema
/**Create a GraphQL Schema */

const schema = new graphql.GraphQLSchema({

    query: queryType,

    mutation: mutationType 

});
 Step 6: Enable CORS in the server

To make a connection between the client side Vue.js application and the GraphQL backend API, we’ll need to enable Cross-Origin resource Sharing (CORS) in the server because the two are using different ports.

/**Enable CORS in the server */

const cors = require('cors');

app.use(cors())

 Finally, mount the /graphql endpoint by running the Express server on port 4000.

/**Mount the /graphql endpoint and run it on port 4000 */

app.use("/graphql", ExpressGraphQL({ schema: schema, graphiql: true}));

 

app.listen(4000, () => {

    console.log("GraphQL server running at http://localhost:4000.");

});/**Mount the /graphql endpoint and run it on port 4000 */

app.use("/graphql", ExpressGraphQL({ schema: schema, graphiql: true}));

 

app.listen(4000, () => {

    console.log("GraphQL server running at http://localhost:4000.");

});

Save the index.js file  and launch the server by running the below command on your terminal.

node index.js  

 PART 2: Creating a Vue CRUD Single Page Application

In this second part of the tutorial, we will create a Vue.js Application using the Vue CLI 3 which creates the default project files. We’ll then customize the application user interface to display the GraphQL data and enable form creation, update, and deletion of the same.

Step 1: Create a Vue Project

To create a new Vue project, we need to install Vue CLI on the development machine. Open a new instance of the terminal and run the below commands.  

npm install -g @vue/cli

vue create vue-graphql-demo

Create a Vue application by selecting the default preset. This will generate project files. To start the development server, run the below commands.

cd vue-graphql-demo

npm run serve

 The default Vue app will then run on the http://localhost:8080/

Vue.js App

Step 2: Install the Apollo Client

Apollo Client is a set of utilities that enables you to use GraphQL in your applications by providing a state management library for JavaScript to manage both local and remote GraphQL data. This then fetches, caches, modifies, and updates the application data automatically on the UI.

To install the Apollo client in the project, navigate back to the terminal and run the below command:

npm install --save vue-apollo graphql apollo-boost

Once Apollo client is installed, open the project folder in your preferred development IDE and open src/main.js file. Create an instance of the Apollo client and connect it to the GraphQL server started in part 1, running at http://localhost:4000/graphql

import Vue from 'vue'

import App from './App.vue'

import ApolloClient from "apollo-boost"

import VueApollo from "vue-apollo"

 

/**create an instance of the Apollo client and connect it to our GraphQL server  */

const apolloClient = new ApolloClient({

  uri: "http://localhost:4000/graphql"

})

 

Vue.use(VueApollo)

 

const apolloProvider = new VueApollo({

  defaultClient: apolloClient,

})

 

Vue.config.productionTip = false

 

new Vue({

  render: h => h(App),

  apolloProvider,

}).$mount('#app') 
Step 3: Consume the GraphQL API

Open the src/App.vue component and start customizing the application. The 3 parts of the file include the template, script, and style. The template section is used to define the user interface using HTML mark-up language, which comprises the contacts table, data input-form, and input buttons for user-enabled events such as Create, Select, Update, and Delete functions (CRUD).

Copy and paste the below code at the top of the App.vue file.

<template>

  <div id="app">

  <h1>CRUD APPLICATION WITH VUE.js and GRAPHQL APOLLO</h1>

  <!-- table for storing contact records -->

  <table border='1' width='100%' style='border-collapse: collapse;'>

   <tr>

     <th>First Name</th>

     <th>Last Name</th>

     <th>Email</th>

     <th>Actions</th>

   </tr>

 

   <!-- use Vue for loop fetch every row record of the contact -->

   <tr v-for='contact in contacts'>

     <td>{{ contact.firstName }}</td>

     <td>{{ contact.lastName }}</td>

     <td>{{ contact.email }}</td>

     <td>

         <!-- Event buttons to select and delete individual contact records -->

      <input type="button" @click="selectContact(contact)" value="Select" id="selectBtn">

      <input type="button" @click="deleteContact(contact.id)" value="Delete" id="deleteBtn">

     </td> 

   </tr>

 </table>

 

 </br>

 <!-- Form Input to create or update contacts -->

    <form>

      <label>First Name</label>

      <input type="text" name="firstName" v-model="firstName">

      </br>

 

      <label>Last Name</label>

      <input type="text" name="lastName" v-model="lastName">

      </br>

 

      <label>Email</label>

      <input type="email" name="email" v-model="email">

      </br>

      

      <input v-if="!id" type="button" @click="createContact(firstName, lastName, email)" value="Add">

      <input v-if="id" type="button" @click="updateContact(id, firstName, lastName, email)" value="Update">

      <input type="button" @click="clearForm()" value="Clear">

    </form>

 

</div>

</template>

 

The second section of the app.vue component is the Script.

Within the script tag, import the gpl from the graphql-tag package. The script tag contains 3 sections data(), apollo, and methods. Within the data() function, we define the four component variables of the contact object- id, firstname, lastname, and email. These variables will be bound to the HTML form to create a new contact. Paste the below code to create an instance of the data() function.

import gql from 'graphql-tag'

export default {

  name: 'app',

  data(){

    return {

      id: null,

      firstName: '',

      lastName: '',

      email: ''}

  },

Next, add the Apollo object to the component with the query for reading contacts from the GraphQL API. In the apollo object, add contacts attributes that will hold the query results. The apollo object attributes will be used to display the contacts in the template. 

Paste the below code within the script.

 apollo: {

    contacts: gql`query {

      contacts {

        id,

        firstName,

        lastName,

        email

      }

    }`,

  },

To send the mutation queries for creating, updating, and deleting data, add the below methods.

methods: {

    createContact(firstName, lastName, email){

      console.log(`Create contact: ${email}`)

      this.$apollo.mutate({

          mutation: gql`mutation createContact($firstName: String!, $lastName: String!, $email: String!){

            createContact(firstName: $firstName, lastName: $lastName, email: $email) {

              id,

              firstName,

              lastName,

              email}

          }`,

          variables:{

            firstName: firstName,

            lastName: lastName,

            email: email

          }

        }

      )

      location.reload();

    },

    updateContact(id, firstName, lastName, email){

      console.log(`Update contact: # ${id}`)

      this.$apollo.mutate({

          mutation: gql`mutation updateContact($id: ID!, $firstName: String!, $lastName: String!, $email: String!){

            updateContact(id: $id, firstName: $firstName, lastName: $lastName, email: $email)

          `,

          variables:{

            id: id,

            firstName: firstName,

            lastName: lastName,

            email: email

          }

        }

      )

      location.reload();

    },

    deleteContact(id){

      console.log(`Delete contact: # ${id}`)

      this.$apollo.mutate({

          mutation: gql`mutation deleteContact($id: ID!){

            deleteContact(id: $id)

          }`,

          variables:{

            id: id,

          }

        }

      )

      location.reload();

    },

    selectContact(contact){

      this.id = contact.id;

      this.firstName = contact.firstName;

      this.lastName = contact.lastName;

      this.email = contact.email;

    },

    clearForm(){

      this.id = null;

      this.firstName = '';

      this.lastName = '';

      this.email = '';

    }    

  }

In all the above methods, we use this.$apollo.mutate() method to send mutations to the GraphQL server then call location.reload() method to reload the page.

The last section is the style. Here, use CSS to style the HTML components of the template. Paste the sample code below  and customize your app.

<style>

#app {

  font-family: Avenir, Helvetica, Arial, sans-serif;

  -webkit-font-smoothing: antialiased;

  -moz-osx-font-smoothing: grayscale;

  text-align: center;

  color: #2c3e50;

  margin-top: 60px;

}

 

#deleteBtn{

  background-color:red;

}

#selectBtn{

  background-color:blue;

}

</style> 

Launch the Vue SPA

Navigate back to the terminal and launch the Vue application using the below command:

npm run serve

The below is the screenshot of the UI after adding sample data.

CRUD App with Vue.js and GraphQL Apollo

Edit/Update Functionality

Edit Update Functionality

Select a particular record. This will generate a pre-filled form with the selected data and an update button to use in case of any edits. 

Conclusion

In this tutorial, we’ve learned how to build a GraphQL API server, build a Vue CRUD SPA, and use Apollo client to consume the GraphQL API. Find the complete Source Code in this repository. 

 

 


Faith Kilonzi is a full-stack software engineer, technical writer, and a DevOps enthusiast, with a passion for problem-solving through implementation of high-quality software products. She holds a bachelor’s degree in Computer Science from Ashesi University. She has experience working in academia, fin-tech, healthcare, research, technology, and consultancy industries in both Kenya, Ghana, and in the USA. Driven by intellectual curiosity, she combines her passion for teaching, technology, and research to create technical digital content.


Discussion

Click on a tab to select how you'd like to leave your comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Menu
Skip to toolbar