Intro
What Is Node
Node lets Javascript run outside the browser and interact with the file system. You can use it to build command line tools, to automate tasks, and to build a web server, among other things.
What are we going to do?
We are running through a series of tutorials with the cringeworthy title “LEARN YOU THE NODE.JS FOR MUCH WIN!”

Sponsor
The Tech Academy
The Tech Academy is a licensed career school that teaches students Computer Programming and Web Development. The Tech Academy offers 4 different bootcamp tracks that include: Frontend Web Development, Python, C# & .NET Framework, and the Software Development Bootcamp, which is a comprehensive bootcamp covering all other bootcamp tracks.
What sets The Tech Academy apart from other coding bootcamps is the comprehensive curriculum, open enrollment, flexible scheduling options, online or in-person training options, self-paced study and exceptional job placement training. At the end of our program, graduates are well-rounded, full-stack junior developers!
Tweet our sponsors to let them know that you appreciate them
@thetechacad
The App
Start here: https://codepen.io/raddevon/pen/abzEpOG
Fork the pen. You will need to create a Codepen account if you don’t have one already.
Step 1- Quick test
Not really our app, but we’re just seeing how Vue uses data in templates
<div class="app">
<h1>Notes</h1>
<p>{{message}}</p>
</div>const app = new Vue({
el: '.app',
data: {
message: 'Hello Vue!',
},
});Step 2- Note titles & localStorage connection
In this step, we’re going to write a way to store the notes and to display their titles. Think about what a note looks like and how we will store it. My concept of a note: simply a title and the contents. Store as an array in localStorage. Site-specific storage area that lives inside the browser. Notes will be on a given computer as opposed to saved in a database like a lot of web applications you use. Limited usefulness but much easier to code. Still useful in a lot of cases.
Hook this up to localStorage. That means, we want to save notes into localStorage and load notes into the application from localStorate. Start with loading.
Use a lifecycle hook. When you were a kid, people asked you what you wanted to be when you grow up. Maybe you said a doctor, a lawyer, or a software developer. A lifecycle hook is like that. What should happen at a certain point in our Vue component’s life? We define that with a lifecycle hook. Hook into the lifecycle by defining a function on the Vue component named for the lifecycle stage we want to hook into.
Lifecycle hooks: https://vuejs.org/v2/api/#Options-Lifecycle-Hooks
We will use mounted because that’s what Vue documentation uses in this scenario. Might also be able to use created. mounted is the point at which the Vue component is hooked into the DOM. Most of the time, you can name your functions whatever you like. This function must be named for the lifecycle stage you want to hook into. When Vue hits that point in the component’s life cycle, it will call the function, executing whatever code you’ve written inside it. We will load the notes from localStorage when the component is mounted.
That covers loading. Now, we’ll use a watch to save new notes or changes to existing notes. watch is an object on the Vue component with functions named for the data properties you want to watch. We’ll make one for notes and update localStorage when it changes.
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
<ul>
<li v-for="note in notes">{{note.title}}</li>
</ul>
</nav>
</div>
</div>const app = new Vue({
el: '.app',
data: {
notes: [],
},
mounted() {
if (localStorage.notes) {
this.notes = localStorage.notes;
}
},
watch: {
notes(newNotes) {
localStorage.notes = newNotes;
},
},
});Step 3- Note empty state
The app doesn’t really look like much because there are no notes. It would be nice if it told us this. We can use v-if directive to render different elements depending on whether we have notes.
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
**
<p v-if="!notes.length">No notes saved</p>
**
<ul **v-if="notes.length" **>
<li v-for="note in notes">{{note.title}}</li>
</ul>
</nav>
</div>
</div>Time out to test
To test, I’m going to use the console to put something in localStorage notes key the app is looking at.
Open CodePen console. (If you were building an app outside Codepen, you’d do the exact same thing inside the Chrome dev tools.) Notes should be an array, so
localStorage.notes = [The array holds the notes objects. Ultimately, each note will have a title and a contents property. We’re only using the titles so far, but we’re going to go ahead and add this note’s contents since we’ll need to test that later.
localStorage.notes = [{ title: 'Test', contents: 'Test note contents' }];We have to refresh to load these new results in CodePen because the app doesn’t update its data from localStorage. Data only flows from the app to localStorage except on initial load. The app won’t ever need to grab data from localStorage during normal usage. If you do that, you’ll see [object Error] in the console. That’s not a very useful error, but we know something broke. In the app, it looks like we still don’t have notes stored.
To figure out what the problem is, let’s start investigating what we did most recently which is setting the notes value in localStorage. We can inspect it just by typing it into the console.
localStorage.notes;The result is [object Object]. That’s obviously not what we put in localStorage. I’ve apparently forgotten exactly how to use localStorage. I would normally go to MDN and read up on localStorage, but I’ve made this same mistake several times, so I remember what my mistake is: localStorage can only store strings.
We can fix this by stringifying the value we store (converting it to a string) and parsing the value that comes out (taking the string and converting it back into an object.
mounted() {
if (localStorage.notes) {
this.notes = **JSON.parse**(localStorage.notes);
}
},
watch: {
notes(newNotes) {
localStorage.notes = **JSON.stringify**(newNotes);
}
}We now need to go back to the console and put some test data in localStorage again, but we need to stringify it this time.
localStorage.notes = JSON.stringify([
{ title: 'Test', contents: 'Test note contents' },
]);Refresh the app again and you should see the note title. That means what we’ve written so far works!
Step 4- Note display
The way I’d like this to work is that you have a list of titles. When you click on one, that note’s contents is displayed. The first thing I’m going to do is make the titles clickable.
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
**<button @click="currentNote = note">{{note.title}}</button>**
</li>
</ul>
</nav>
</div>
</div>All we’re doing is setting a currentNote data attribute equal to the note whose title is being clicked. Vue lets you set event listeners using a directive (like a custom HTML attribute, this being one that’s included as part of Vue). You use the v-on directive to do it which would make the button look like.
<button v-on:click="currentNote = note">{{note.title}}</button>Since binding an event is so common, Vue offers a shorthand for doing it: @ followed by the event you’re binding. That’s what I’m using instead of the longer syntax. Either way, it serves the same purpose.
Problem with setting currentNote is that we don’t have that data attribute yet in our Vue component. We can create it, and we’ll set it initially to null since there shouldn’t be an active note when the app starts.
const app = new Vue({
el: '.app',
data: {
notes: [],
**currentNote: null**
},
mounted() {
if (localStorage.notes) {
this.notes = JSON.parse(localStorage.notes);
}
},
watch: {
notes(newNotes) {
localStorage.notes = JSON.stringify(newNotes);
}
}
});Now we need to actually display the active note’s contents.
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
<button @click="currentNote = note">{{note.title}}</button>
</li>
</ul>
</nav>
**
<p v-if="currentNote">{{currentNote.contents}}</p>
**
</div>
</div>While we’re at it, let’s display the title too and wrap both those elements into a single element so we can align it to the right of the list. We’ll also move the conditional to the container since we don’t want any of it to display without a currentNote.
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
<button @click="currentNote = note">{{note.title}}</button>
</li>
</ul>
</nav>
**
<div v-if="currentNote" class="current-note">
<h2>{{currentNote.title}}</h2>
**
<p>{{currentNote.contents}}</p>
**
</div>
**
</div>
</div>It would be nice if our users could see in the note title list which note is the current note. We can do that by binding the class attribute for our button. Binding an attribute means we’re looking at data in our Vue app to decide what an HTML attribute should be set to. You do data binding with v-bind:<attribute>=<value>. Just like event binding, it has a shorthand to make it look a little cleaner. You can just delete the v-bind part and leave the rest.
You could pass this a function that returns the string you want to set as the class on this element. You could pass it a Javascript variable that holds a string which should be the value of the attribute. We’re going to pass it an object instead. To explain how Vue uses the object, it’s best to see it in action.
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
<button
@click="currentNote = note"
**:class="{active: note === currentNote}"
**
>
{{note.title}}
</button>
</li>
</ul>
</nav>
<div v-if="currentNote" class="current-note">
<h2>{{currentNote.title}}</h2>
<p>{{currentNote.contents}}</p>
</div>
</div>
</div>The keys in the object are the potential values of the attribute. The values of the object are the conditions under which that value is set. Here, we’re adding the active class to the button if note (that is, the note associated with this button) and currentNote are the same object.
Step 5- Note editing
You can’t have a notes app with read-only notes, so lets add editing. The first thing I’m going to do is to convert the object displaying the note body to a textarea instead of a p. This will allow me to create a two-way data binding between the text area and the actual note data in the Vue app.
Right now, We’re just dropping that data into the p, but there’s nothing that will make it update if what’s in the p changes. v-model will do that. If the contents of the note changes, it will automatically update the textarea, and if the contents of the textarea changes, it will automatically update the data in the app.
Here’s the HTML after the change:
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
<button
@click="currentNote = note"
:class="{active: note === currentNote}"
>
{{note.title}}
</button>
</li>
</ul>
</nav>
<div v-if="currentNote" class="current-note">
<h2>{{currentNote.title}}</h2>
**<textarea v-model="currentNote.contents"></textarea>**
</div>
</div>
</div>Instead of currentNote.contents in curly braces, it’s now being passed to the v-model directive so that Vue knows to keep the two values in sync.
Let’s see if this works. Open test note. Make change. Refresh. We refresh because, otherwise, we don’t know if the data is being saved to localStorage. Now, the note has reverted back, so something is wrong.
To start to figure out what it is, I want to flip over to the Javascript and look for the place in our code where we’re writing to localStorage. That’s in the watch on notes.
const app = new Vue({
el: '.app',
data: {
notes: [],
currentNote: null
},
mounted() {
if (localStorage.notes) {
this.notes = JSON.parse(localStorage.notes);
}
},
watch: {
**notes(newNotes) {
localStorage.notes = JSON.stringify(newNotes);
}**
}
});The text area displaying the note is bound to the note data in the app. This watcher watches that note data. I want to verify the watcher is running when we change the text area. To do that, I’m just going to log out a message inside the watch callback function. Whenever that runs, it should log a message to the console to let us know.
const app = new Vue({
el: '.app',
data: {
notes: [],
currentNote: null
},
mounted() {
if (localStorage.notes) {
this.notes = JSON.parse(localStorage.notes);
}
},
watch: {
notes(newNotes) {
**console.log('Updating local storage');**
localStorage.notes = JSON.stringify(newNotes);
}
}
});Save and make some note changes. No log.
I researched this problem and learned that Vue watched do not watch inside objects or arrays by default. They do have an option to turn that on though. Here’s what we need to do to fix this.
const app = new Vue({
el: '.app',
data: {
notes: [],
currentNote: null
},
mounted() {
if (localStorage.notes) {
this.notes = JSON.parse(localStorage.notes);
}
},
watch: {
**notes: {
handler: (newNotes) => {
localStorage.notes = JSON.stringify(newNotes);
},
deep: true
}**
}
});We were using a shorthand for setting notes before. By breaking it out and passing the watch callback in an object with the handler key, we are also able to set the deep option to true which tells Vue to watch for changes inside the notes array.
Change again. Refresh to show changes are saved.
I’ll do the same thing we the title so that those can also be edited.
<div class="app">
<h1>Notes</h1>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
<button
@click="currentNote = note"
:class="{active: note === currentNote}"
>
{{note.title}}
</button>
</li>
</ul>
</nav>
<div v-if="currentNote" class="current-note">
<input v-model="currentNote.title" />
<textarea v-model="currentNote.contents"></textarea>
</div>
</div>
</div>Step 6- Create Notes
I’ll start by adding a button next to the heading that allows new notes to be created. I need the button to do a couple of things. First, it should create a new note with empyt title and contents. Then, it should make that the current note so that it’s easy to start editing right away. It might also be nice if it focus the title so you can type that immediately.
I could try to do all this right in an event binding directive, but that seems like it would be a lot of code to stuff in there. I’ll create a method on the Vue instance that I can call from the event binding instead.
const app = new Vue({
el: '.app',
data: {
notes: [],
currentNote: null
},
**methods: {
createNote() {
const newNote = {title: "", contents: ""};
this.notes.push(newNote);
this.currentNote = newNote;
}
},**
mounted() {
if (localStorage.notes) {
this.notes = JSON.parse(localStorage.notes);
}
},
watch: {
notes: {
handler: (newNotes) => {
localStorage.notes = JSON.stringify(newNotes);
},
deep: true
}
}
});That takes care of everything except focusing the field. To do that, I need to have a way to refer to that field. I could use vanilla Javascript to select it, but Vue has a fancy way to do this called refs that we’ll use instead.
<div class="app">
<h1>Notes</h1>
<button ****class="create-new-button">Create New</button>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
<button
@click="currentNote = note"
:class="{active: note === currentNote}"
>
{{note.title || "Untitled Note"}}
</button>
</li>
</ul>
</nav>
<div v-if="currentNote" class="current-note">
<input v-model="currentNote.title" **ref="noteTitle" ** />
<textarea v-model="currentNote.contents"></textarea>
</div>
</div>
</div>Now I can go back and use this ref to focus the field after we make the new note. That lets the user click the create button and then start typing the title.
const app = new Vue({
el: '.app',
data: {
notes: [],
currentNote: null
},
methods: {
createNote() {
const newNote = {title: "", contents: ""};
this.notes.push(newNote);
this.currentNote = newNote;
**this.$refs.noteTitle.focus();**
}
},
mounted() {
if (localStorage.notes) {
this.notes = JSON.parse(localStorage.notes);
}
},
watch: {
notes: {
handler: (newNotes) => {
localStorage.notes = JSON.stringify(newNotes);
},
deep: true
}
}
});Now, I need to hook it up with an event binding on the button.
<div class="app">
<h1>Notes</h1>
<button **@click="createNote" ** class="create-new-button">Create New</button>
<div class="notes">
<nav>
<p v-if="!notes.length">No notes saved</p>
<ul v-if="notes.length">
<li v-for="note in notes">
<button
@click="currentNote = note"
:class="{active: note === currentNote}"
>
{{note.title || "Untitled Note"}}
</button>
</li>
</ul>
</nav>
<div v-if="currentNote" class="current-note">
<input v-model="currentNote.title" ref="noteTitle" />
<textarea v-model="currentNote.contents"></textarea>
</div>
</div>
</div>This kinda works, but let me show you a bug: if you click the create button before you have clicked on any of the notes, Vue complains that undefined has no focus method. That’s because we didn’t have a currentNote, so, there initially was no noteTitle since that’s behind a v-if. None of the note display elements get rendered at all if we don’t have a currentNote.
As soon as Vue catches up to us setting the current note, it will update the DOM and everything will be good. We’ll be able to focus that element. How can we wait for that. You might default to using setTimeout to try to wait for something like this, but there’s almost always a better way. With setTimeout, you’re just guessing how long it will take. Either you’re waiting way to long and potentially making your app feel sluggish, or you’re waiting some time that’s enough on some computers and not on others causing your app to feel buggy.
Vue give us a $nextTick promise on each Vue component which resolves when it’s finished with all its current async tasks (like updating the DOM). We can use this to wait for the field to be rendered before trying to focus it.
const app = new Vue({
el: '.app',
data: {
notes: [],
currentNote: null
},
methods: {
**async** createNote() {
const newNote = {title: "", contents: ""};
this.notes.push(newNote);
this.currentNote = newNote;
**await this.$nextTick();**
this.$refs.noteTitle.focus();
}
},
mounted() {
if (localStorage.notes) {
this.notes = JSON.parse(localStorage.notes);
}
},
watch: {
notes: {
handler: (newNotes) => {
localStorage.notes = JSON.stringify(newNotes);
},
deep: true
}
}
});Now, you can click Create New even when we don’t have a currentNote set and you’ll still get focus on the title.
Extra Credit
- Delete notes
- Note sorting (alpha, by created time)
- Manual sorting (click-and-drag)
- Auto-delete empty notes (no title, no contents)