{{announcement.body}}
{{announcement.title}}

How to Build a Serverless App With Vue, Azure Functions and FaunaDB Part 3

DZone 's Guide to

How to Build a Serverless App With Vue, Azure Functions and FaunaDB Part 3

In the final part of this three-part series, we build out the frontend of our application with Vue.js in order to display our notes.

· Web Dev Zone ·
Free Resource

looking-glass-lake

The FaunaDB database is up and running and the Azure Functions are developed and tested locally. In the final part of this three-part series, we will build the Vue.js app to manage notes by creating new ones and reading existing ones. The app will be communicating with the Azure Functions which will be communicating with the FaunaDB database.

Note: You can find parts one and two of this series at their respective links. 

Create a New Vue CLI App

Let’s start by creating a new Vue.js app using the Vue CLI. If you don’t have the CLI installed locally on your machine, run the command:

 npm install -g @vue/cli 

With CLI installed, create a new project. Run the command:

 vue create notes-app-azure-serverless 

You will be prompted to pick a preset. Select the  default(babel, eslint). The CLI will generate a new Vue app for you. Open the new app inside your IDE, and let’s continue coding. The final source code for the app can be found at this GitHub repo.

You may also like: Vue.js Tutorial 1 — Hello Vue.

Build the App component

Before we start coding the app, we need to install the following NPM packages. Run the following command:

 npm install --save axios@0.19.0 lodash@4.17.15 marked@0.7.0 

Replace the content of App.vue with the following:

<template>
  <div id="app">
    <Notes></Notes>
  </div>
</template>

<script>
import Notes from './components/Notes.vue'

export default {
  components: {
    Notes
  }
}
</script>


The Notes component is the container component hosting the entire app. Let’s have a look at the final app layout before we move on and dissect the sections involved in building the app.

  • The left side of the page lists all saved Note documents.
  • The middle section of the page is your Markdown editor.
  • The right side of the page renders the markdown text in plain HTML.

Build the Notes Component

The Notes component hosts the rest of the components in this app. Let’s take a look.

<template>
  <div class="notes-container">
    <NotesList
      :notes="notes"
      class="notes-container__list"
      @save-note="saveNote"
      @select-note="selectNote"
    ></NotesList>
    <NotesCreate
      :note="currentNote"
      @note-content-change="noteContentChanged"
      class="notes-container__create"
    ></NotesCreate>
  </div>
</template>

<script>
import NotesCreate from './NotesCreate';
import NotesList from './NotesList';
import axios from 'axios';

export default {
  data () {
    return {
      notes: [],
      currentNote: null
    }
  },

  components: {
    NotesCreate,
    NotesList
  },

  mounted () {
    axios.get('http://localhost:7071/api/notes-read-all')
      .then(response => {
        this.notes = response.data.map(note => note.data);
      });
  },

  methods: {
    selectNote (note) {
      this.currentNote = note;
    },
    noteContentChanged (note) {
      this.currentNote = note;
    },
    saveNote () {
      const note = {
        title: this.currentNote.substring(0, 25),
        body: this.currentNote
      };

      axios.post('http://localhost:7071/api/notes-create', note).then(() => {
        this.notes.push(note);
        this.currentNote = null;
      });
    }
  }
}

</script>


The markup uses both the NotesList and NotesCreate components.

The Notes component handles two events emitted by the NotesList component:

  •  select-note.
  •  save-note.

The select-note event is emitted when the user selects an existing Note. The Notes component handles this event by updating a local variable named with the selected Note.

The NotesCreate component has a binding, named  note, that is bound to the currentNode local variable. Hence, when the currentNote value changes, the NotesCreate component is refreshed with a new value for note binding.

The save-note event is emitted when the user clicks the Save Note button. The Notes component handles this event and communicates with the Azure Function via a REST API URL to create the Notes document. 

The saveNote() function prepares a new Note object and sends a POST request to the Azure Function notes-create, using axios.post(), and passes along the request URL and the Note  object as a request payload.

Moreover, the Notes component handles a single event emitted by the NotesCreate component:

  •  note-content-change

The note-content-change event is emitted when the user changes the content of the editor. This way, the Notes component always has the latest copy of the Note content.

The event handler for note-content-change event updates the local variable, currentNote, with the latest content received from the NotesCreate component.

The NotesList component has an input binding, named notes. The Notes component uses a Vue.js component life-cycle hook called the  mounted() to issue a GET request to the Azure Function  notes-read-all using  axios.get(). The response is then bound to a local data variable named  notes.

Build the NotesList Component

The NotesList component is responsible for listing the Notes on the left-side section of the app as shown in the diagram above.

<template>
  <div class="notes">
    <button
      class="btn btn-new-note"
      @click="saveNewNote"
    >Save Note</button>
    <div class="
      notes__items">
      <Note
        v-for="(note, index) in notes"
        :key="index"
        v-bind="note"
        class="select-note"
        @select-note="selectNote($event, note)"
      ></Note>
    </div>
  </div>
</template>

<script>
import Note from './NoteItem';
export default {
  props: {
    notes: {
      type: Array
    }
  },

  components: {
    Note
  },

  methods: {
    saveNewNote () {
      this.$emit('save-note');
    },
    selectNote ($event, note) {
      this.$emit('select-note', note.body);
    }
  }
}

</script>


The component hosts the Save Note button. When the user clicks this button, it emits the  save-note event that is being handled by the Notes component, as previously shown.

The component receives an Array of Note items as input. It loops over each and every Note item, and renders it inside its own NoteItem component.

When the user clicks a single Note item, the select-note event is emitted and is eventually handled by the Notes component, as shown above.

The NoteItem component has the following structure:

<template>
  <div
    class="notes__item"
    @click.prevent="selectNote"
  >
    <h4>{{ title }}</h4>
    <p class="note-body">{{ body }}</p>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String
    },

    body: {
      type: String
    }
  },

  methods: {
    selectNote () {
      this.$emit('select-note', this.body);
    }
  }
}

</script>


The component receives two input properties: Note title and Note body. It renders the details of a single Note object. 

When the user selects a single Note item, the select-note event is emitted, and handled, by NotesList and Notes component, respectively. 

Build the NotesCreate Component

The Markdown editor and the rendering section are both hosted inside the NodeCreate component:

<template>
  <div class="editor">
    <div class="editor__md">
      <textarea
        name="markdown"
        :value="content"
        @input="update"
        placeholder="Type your note here ..."
      ></textarea>
    </div>
    <div class="editor__compiled-md">
      <div v-html="compiledMarkdown"></div>
    </div>
  </div>
</template>

<script>
import marked from 'marked';
import _ from 'lodash';

export default {
  props: {
    note: {
      type: String
    }
  },

  computed: {
    compiledMarkdown () {
      return this.content ? marked(this.content) : '';
    },

    content () {
      return this.note;
    }
  },

  methods: {
    update: _.debounce(function (e) {
      this.$emit('note-content-change', e.target.value);
    }, 300
  }
}
</script>


The editor is made up of a single textarea. The textarea has two bindings:

  •  :value is bound to a local variable named content.
  •  @input event is bound to a local function named update.

This component receives an input parameter named note of data type String. The note property renders inside the editor.

The component defines a Computed property, named content. This property returns the value of the note input property. 

Note: Using a computed property to wrap an input property, and then bind the computed property to the markup, is recommended when developing Vue.js apps. This act makes sure the input property is not mutated whatsoever.

When the user changes the text inside the editor, the component emits the note-content-change event which is eventually handled by the Notes component. 

The other section of this component is where we render the compiled Markdown:

<div class="editor__compiled-md">
      <div v-html="compiledMarkdown"></div>
</div>


The compiled Markdown is controlled via a Computed property named compiledMarkdown

Let’s have a look at this property:

compiledMarkdown () {
      return this.content ? marked(this.content) : '';
 }


The compiledMarkdown property is reactive by nature and generates a new value every time the this.content variable changes. It makes use of the marked() function provided by  marked NPM package. It compiles the Markdown text into HTML markup.

Conclusion

We can develop web apps without building a backend server for it! By using Azure Functions as a REST API, hosted in the cloud, and using FaunaDB as a database service hosted in the cloud, we can build a full-fledged application!

This is what serverless is about—building apps in the cloud, and interacting with other services in the cloud, to provide all the functions needed by the app, without having to manage a single server anywhere.


Further Reading

Topics:
web dev ,vue ,vue cli ,serverless ,faunadb ,azure ,azure function ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}