Creating a Simple Web App with HarperDB, Express, and Vue
Tags: node, vue, sql, webdev
Published: 2020-07-13 23:32:07

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](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:

```js
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:

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

and:

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

And we add the CORS middleware with:

```js
var cors = require('cors')
```

and:

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

Next in `dog.js`, we create our routes.

To do that, we add:

```js
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:

```vue
<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:

```vue
<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](https://dev-to-uploads.s3.amazonaws.com/i/c66dko3spgce2t0dq2os.png)

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.