How to Build a Serverless App With Vue, Azure Functions and FaunaDB Part 3
Join the DZone community and get the full member experience.
Join For FreeThe 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 namedcontent
. -
@input
event is bound to a local function namedupdate
.
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
Opinions expressed by DZone contributors are their own.
Trending
-
Observability Architecture: Financial Payments Introduction
-
Microservices With Apache Camel and Quarkus
-
RBAC With API Gateway and Open Policy Agent (OPA)
-
What ChatGPT Needs Is Context
Comments