Creating a Simple Web App with HarperDB, Express, and Vue

John Au-Yeung - Jul 13 '20 - - Dev Community

HarperDB is a scalable SQL and NoSQL database that we can use to store data.

We can make queries including more complex ones like joins.

It’s useful for creating reports and data analysis with the versatility of its API.

It lets us control the database via HTTP requests instead of direct queries.

In this article, we’ll look at how to create a simple web app with HarperDB, Express, and Vue.

Getting Started

We first install the Postman HTTP client.

Then we download the Postman collection from https://examples.harperdb.io/?version=latest.

It lets us look at what features are available and how to issue commands to use them.

Then we install HarperDB by installing Node 12.16.1.

If we use nvm, we can run nvm install 12.16.1 .

Then we switch to that version by running:

nvm use 12.16.1

Next, we install HarperDB by running:

npm install -g harperdb

Then we run it by running:

harperdb run

Creating Our App

Next, we create our project with Express and Vue.

It’ll let us read, create, update, and delete data about dogs.

To do that, we create a project folder with the backend and frontend folders.

In the backend folder, we run:

npx express-generator

in the backend folder.

Then we install the cors middleware to let our front end use the API and axios to talk to HarperDB by running:

npm i cors axios

in the same folder.

In the frontend folder, we run:

npx vue create .

to create the Vue project.

We just go with the default options.

Back End

Now we can work on the back end.

To interact with HarperDB, we just make HTTP requests.

We first rename user.js in the routes folder to dog.js .

Then in app.js , we write:

var createError = require('http-errors');  
var express = require('express');  
var path = require('path');  
var cookieParser = require('cookie-parser');  
var logger = require('morgan');  
var cors = require('cors')var indexRouter = require('./routes/index');  
var dogsRouter = require('./routes/dogs');var app = express();

// view engine setup  
app.set('views', path.join(__dirname, 'views'));  
app.set('view engine', 'jade');app.use(logger('dev'));  
app.use(express.json());  
app.use(express.urlencoded({ extended: false }));  
app.use(cookieParser());  
app.use(express.static(path.join(__dirname, 'public')));  
app.use(cors())
app.use('/', indexRouter);  
app.use('/dogs', dogsRouter);// catch 404 and forward to error handler  
app.use(function(req, res, next) {  
  next(createError(404));  
});

// error handler  
app.use(function(err, req, res, next) {  
  // set locals, only providing error in development  
  res.locals.message = err.message;  
  res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page  
  res.status(err.status || 500);  
  res.render('error');  
});

module.exports = app;

to add the dogs route with:

var dogsRouter = require('./routes/dogs');

and:

app.use('/dogs', dogsRouter);

And we add the CORS middleware with:

var cors = require('cors')

and:

app.use('/dogs', dogsRouter);

Next in dog.js, we create our routes.

To do that, we add:

var express = require('express');  
var router = express.Router();  
const axios = require('axios');

const HDB_ENDPOINT = 'http://localhost:9925';  
const TOKEN = 'Basic SERCX0FETUlOOnBhc3N3b3Jk';  
const headers = {  
  Authorization: TOKEN,  
  Accept: '*/*',  
  'Accept-Encoding': 'gzip, deflate, br',  
  'Connection': 'keep-alive',  
  'Content-Type': 'application/json'  
}

router.get('/', async (req, res) => {  
  try {  
    const { data } = await axios.post(HDB_ENDPOINT, {  
      "operation": "sql",  
      "sql": `SELECT * FROM dev.dog`  
    }, {  
      headers  
    })  
    res.json(data);  
  } catch (error) {  
    console.log(error)  
  }});

router.get('/:id', async (req, res) => {  
  const { id } = req.params;  
  const { data } = await axios.post(HDB_ENDPOINT, {  
    "operation": "sql",  
    "sql": `SELECT * FROM dev.dog where id = ${id}`  
  }, {  
    headers  
  })  
  res.json(data);  
});

router.post('/', async (req, res) => {  
  const { dog_name, owner_name, age, weight_lbs } = req.body;  
  const { data } = await axios.post(HDB_ENDPOINT, {  
    "operation": "insert",  
    "schema": "dev",  
    "table": "dog",  
    "records": [  
      {  
        dog_name, owner_name, age, weight_lbs  
      }  
    ]  
  }, {  
    headers  
  })  
  res.json(data);  
});

router.put('/:id', async (req, res) => {  
  const { id } = req.params;  
  const { dog_name, owner_name, age, weight_lbs } = req.body;  
  const { data } = await axios.post(HDB_ENDPOINT, {  
    "operation": "update",  
    "schema": "dev",  
    "table": "dog",  
    "records": [  
      {  
        id,  
        dog_name, owner_name, age, weight_lbs  
      }  
    ]  
  }, {  
    headers  
  })  
  res.json(data);  
});

router.delete('/:id', async (req, res) => {  
  try {  
    const { id } = req.params;  
    const { data } = await axios.post(HDB_ENDPOINT, {  
      "operation": "sql",  
      "sql": `DELETE FROM dev.dog WHERE id = '${id}'`  
    }, {  
      headers  
    })  
    res.json(data);  
  } catch (error) {  
    res.json(error)  
  }  
});

module.exports = router;

to the file.

We have the get routes that issue the select command.

The headers have the token, encoding, and content-type headers.

The token is from the Postman sample, which we can use to interact with the local version of HarperDB.

We authenticate with the DB with the token.

If we’re using it in production, then we’ve to make the token configurable.

Then we issue our commands by making the HTTP request with the SQL command to retrieve and delete data.

To create and update, we just send the data we want to add or update.

The operation property in the request payload will tell HarperDB what to do.

Front End

To build the front end, we add the DogForm.vue file to the components folder.

We add the form in the code.

We add:

<template>  
  <div>  
    <form @submit.prevent="submit">  
      <div>  
        <label>Dog Name</label>  
        <input v-model="dog.dog_name" name="dogName" type="text" />  
      </div>  
      <div>  
        <label>Owner Name</label>  
        <input v-model="dog.owner_name" name="ownerName" type="text" />  
      </div>  
      <div>  
        <label>Age</label>  
        <input v-model="dog.age" name="age" type="number" />  
      </div>  
      <div>  
        <label>Weight</label>  
        <input v-model="dog.weight_lbs" name="weight" type="number" />  
      </div>  
      <div>  
        <input type="submit" value="save" />  
        <button type="button" @click="remove">remove</button>  
      </div>  
    </form>  
  </div>  
</template>

<script>  
const axios = require("axios");  
const APIURL = "http://localhost:3000/dogs";

export default {  
  name: "DogForm",  
  props: {  
    dog: {  
      type: Object,  
      default: () => ({})  
    }  
  },  
  data() {  
    return {};  
  },  
  methods: {  
    async submit() {  
      const { dog_name, owner_name, age, weight_lbs, id } = this.dog;  
      if (!id) {  
        await axios.post(APIURL, {  
          dog_name,  
          owner_name,  
          age,  
          weight_lbs  
        });  
      } else {  
        await axios.put(`${APIURL}/${id}`, {  
          dog_name,  
          owner_name,  
          age,  
          weight_lbs  
        });  
      }  
      this.$emit("submitted");  
    }, 

    async remove() {  
      const { id } = this.dog;  
      await axios.delete(`${APIURL}/${id}`);  
      this.$emit("submitted");  
    }  
  }  
};  
</script>

We add the form to let us add, edit, or remove a dog data entry.

The props has the type object with the default value being an object.

We need to have a function that returns an object to specify the default value for objects.

Then we have our submit method that makes a post request if there’s no id, which means it’s new.

If it’s a production app, then we should add some form validation.

If there’s an id, then we update the existing entry.

Also, we have the remove method to remove an entry.

It’s called when we click remove.

All methods emit the submit event, which we listen to in App.vue.

In App.vue , we have:

<template>  
  <div id="app">  
    <DogForm @submitted="getDogs" />  
    <DogForm v-for="dog of dogs" :key="dog.id" :dog="dog" @submitted="getDogs" />  
  </div>  
</template>

<script>  
import DogForm from "./components/DogForm.vue";  
const axios = require("axios");  
const APIURL = "http://localhost:3000/dogs";

export default {  
  name: "App",  
  components: {  
    DogForm  
  },  
  data() {  
    return {  
      dogs: []  
    };  
  },  
  beforeMount() {  
    this.getDogs();  
  },  
  methods: {  
    async getDogs() {  
      const { data } = await axios.get(APIURL);  
      this.dogs = data;  
    }  
  }  
};  
</script>

We use the DogForm for entering and editing entries.

They listen to the submitted event so the new data will be retrieved.

We use the getDogs method to get the data with a get request.

Run Our App

We go to the Postman collection and create the Dog schema and table.

We go to the QuickStart Examples folder in the collection and run the ‘Create dev Schema’ and ‘Create do Table’ requests in the order they’re listed.

Then we run npm start in the backend folder to start the Express app.

And we run npm run dev to in the frontend folder to run the front end app.

Now we get something like:

Dog App

And we can enter what we want.

Conclusion

We can create database-driven apps with HarperDB easily.

The only difference is that we send commands to our database with HTTP requests instead of issuing commands directly.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .