Notes App with Vue and Poi in 20 minutes

Robert Guiscard
Nov 15, 2017 · 4 min read

I am not an expert in Javascript and not familiar with its tool chain. So I often end up using Rails 5.1 with webpacker for developing Javascript application.

Now, I found Poi as a very good solution for people who want to quickly start writing codes instead of setting up development environment.

Here, I use this excellent tutorial “Learn Vuex by Building a Notes App” to introduce how to set up a simple Vue application in 20 minutes.

Note: I wrote these steps from memory and might miss something.

First, create a directory and install poi:

$> mkdir note_app
$> cd note_app
$> yarn init # fill up information. you might not need this step.
$> yarn add poi --dev
$> yarn add vuex

Then, you can copy most files from git of original tutorial. My directory look like this one:

- index.ejs
- node_modules
- package.json
- poi.config.js
- src
- index.js
- components
- App.vue
- Editor.vue
- NotesList.vue
- Toolbar.vue
- vuex
- store.js
- style.css
- yarn.lock

You will notice that it is slightly different from original code. I did some adjustment to make it work with poi.

I have poi.config.js like this:

module.exports = (options, req) => ({
entry: './src/index.js',
webpack(config) {
config.resolve.alias['vue$'] = 'vue/dist/vue.esm.js'
return config

Note that vue.esm.js is used to avoid complaints from Vue about ‘compiler-included build’

index.ejs with minor modification to include bootstrap and app tag:

<!DOCTYPE html>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title><%= htmlWebpackPlugin.options.title %></title>
<% if (htmlWebpackPlugin.options.description) { %>
<meta name="description" content="<%= htmlWebpackPlugin.options.description
<% } %>
<link rel="stylesheet" href="
<link rel="stylesheet" href="styles.css">

You need to enable JavaScript to run this app.


import Vue from 'vue'
import store from './vuex/store.js'
import App from './components/App.vue'
new Vue({
store, // inject store to all children
el: "app",
template: "<app></app>",
components: { App }

App.vue is the same:

<div id="app">
import Toolbar from './Toolbar.vue'
import NotesList from './NotesList.vue'
import Editor from './Editor.vue'
export default {
components: {

The rest three component (Toolbar, NoteLists and Editor) use mapState and mapAction to map actions from vuex store. The HTML templates are all the same.


<div id="toolbar">
<i @click="addNote" class="glyphicon glyphicon-plus"></i>
<i @click="toggleFavorite"
class="glyphicon glyphicon-star"
:class="{starred: activeNote.favorite}"></i>
<i @click="deleteNote" class="glyphicon glyphicon-remove"></i>
import { mapState, mapActions } from 'vuex'
export default {
computed: mapState([
methods: {



<div id="notes-list">
<div id="list-header">
<h2>Notes | coligo</h2>
<div class="btn-group btn-group-justified" role="group">
<!-- All Notes button -->
<div class="btn-group" role="group">
<button type="button" class="btn btn-default"
@click="show = 'all'"
:class="{active: show === 'all'}">
All Notes
<!-- Favorites Button -->
<div class="btn-group" role="group">
<button type="button" class="btn btn-default"
@click="show = 'favorites'"
:class="{active: show === 'favorites'}">
<!-- render notes in a list -->
<div class="container">
<div class="list-group">
<a v-for="note in filteredNotes"
class="list-group-item" href="#"
:class="{active: activeNote === note}"
<h4 class="list-group-item-heading">
{{note.text.trim().substring(0, 30)}}
import { mapActions, mapState } from 'vuex'
export default {
data () {
return {
show: 'all'
computed: {
filteredNotes () {
if ( === 'all'){
return this.notes
} else if ( === 'favorites') {
return this.notes.filter(note => note.favorite)
notes: state => state.notes,
activeNote: state => state.activeNote

methods: {



<div id="note-editor">
import { mapState, mapActions } from 'vuex'
export default {
computed: mapState({
activeNoteText: state => state.activeNote.text,
methods: {


store.js now also includes actions.js in a single file.

mport Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)// the root, initial state object
const state = {
notes: [],
activeNote: {}
// define the possible mutations that can be applied to our state
const mutations = {
ADD_NOTE (state) {
const newNote = {
text: 'New note',
favorite: false
state.activeNote = newNote
EDIT_NOTE (state, text) {
state.activeNote.text = text
DELETE_NOTE (state) {
state.activeNote = state.notes[0]
state.activeNote.favorite = !state.activeNote.favorite
SET_ACTIVE_NOTE (state, note) {
state.activeNote = note
const actions = {
addNote (context) {
editNote (context, e) {
deleteNote (context) {
updateActiveNote (context, note) {
context.commit('SET_ACTIVE_NOTE', note)
toggleFavorite (context) {
// create the Vuex instance by combining the state and mutations objects
// then export the Vuex store for use by our components
export default new Vuex.Store({

For some reasons, I have no poi executable installed. Thus, I just use node to run its cli like this:

node node_modules/poi/bin/cli.js

And the output like this one:

Asset       Size  Chunks                    Chunk Names 44 bytes [emitted]
vendor.js 1.77 MB 0 [big] vendor
client.js 81.6 kB 1 client
manifest.js 31.2 kB 2 [emitted] manifest 35 bytes [emitted]
index.html 720 bytes [emitted]
> Open http://localhost:4000
> On Your Network:
DONE Build 2d8232 finished in 177 ms!

Now, you can open link in your web browser and see the notes app in Vue.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade