I could not find a good example of a TodoMVC that uses in-memory data store (like Redux or Vuex) that is synchronized against a REST backend, so I put together this example. It is based on the simplest TodoMVC using Vue.js which does not require any build steps to run. You can find my code in the bahmutov/vue-vuex-todomvc repo.
The problem
We need to add / remove Todo items in the list and store them on the backend. Our front end framework should work against a data store, but the data store should make calls to actually store the data on the backend. Each Todo item will have a title, a completed boolean flag and a random id, generated on the client. Here is a typical todo item:
1 | { |
There are a LOT of front end solutions to this problem at todomvc.com
excepts they all use browser's localStorage
to keep the data. I wanted to have a more realistic
scenario where a server is actually serving the items.
The web application should do the following 3 things:
- On startup, get the list of items from the server
- When adding an item, it should first post the item to the server, and only then should add the item to the client's data store
- On deleting an item, it should delete the item from the server, and if it has been deleted on the server, then it should delete it on the client side.
In all cases, the server keeps "the true state"; the client-side data store is secondary, and it is updated only if the server has acknowledged the operation.
REST backend
To implement the backend I chose the json-server to create the REST backend with
zero code. Just install it using npm i -S json-server
command,
create a JSON file with the contents shown below and start the server
1 | { |
1 | { |
The server will serve the current folder, which allows us to load the index.html
page
from localhost:3000
.
It also watches the data.json
file, if we change its contents, the server is automatically
restarted. We can use this feature to reset items before each test for example.
We can now GET list of items from the localhost:3000/todos
endpoints,
and we can add new items to it by making a POST request.
We can delete a particular item by making DELETE /todos/:id
request.
Vuex store
The store is pretty simple, here is its data, and the two getters that we expect to be used from the application.
1 | const store = new Vuex.Store({ |
We can also have additional state there, like "loading" indicator, but for this tutorial I am going to skip these details. See app.js for the entire store.
In addition, the store is going to have mutations and actions. Mutation methods can change the "state" object, and will trigger the Vue DOM updates. Thus these methods are synchronous. For example, here are mutations to set the todos array, add a single todo item, and to clear the "newTodo" text.
1 | const store = new Vuex.Store({ |
Actions on the other hand are not mutating the state yet. Actions are called by the outside application code (think UI component code), and can perform asynchronous operations and then call mutations to actually set the new data. Here are three actions to fetch the list of todos from the server and set it on the state, and to clear the "newTodo" string.
I am using axios library to make XHR calls to the backend.
1 | const store = new Vuex.Store({ |
Not every action is asynchronous, and a single action can trigger multiple mutations. For example, if we were setting "loading" state before making an API call, we could do something like this:
1 | loadTodos ({ commit }) { |
Great, how is our store used from the Vue component?
Vue component
First, we tell Vue framework to use Vuex data store.
1 | Vue.use(Vuex) |
Second, when creating a component, we set the store on it. We trigger actions on the store from the Vue component by dispatching them. And we read the data from the store by connecting computed properties to the store's getters.
1 | const app = new Vue({ |
All commands from the DOM elements should go via component's methods. In this case we have just a few methods.
1 | const app = new Vue({ |
These methods are triggered by the DOM events, defined using Vue template language. For example, method "removeTodo" is called when the user clicks on the "class=destroy" element on each item in the list.
1 | <section class="main" v-show="todos.length" v-cloak> |
And that's it - we have an application, where the DOM events are dispatched to the store; inside the store's actions we can make REST calls to the backend. The store after receiving the response mutates the state. Vue connects everything together - whenever the data in the store changes, the "getters" are going to change, which changes the "computed" properties, triggering the DOM update to show the changed data.
In an ASCII diagram, it would be a cycle of calls triggered from the DOM, going via actions to the backend, then back to the action code, etc.
1 | DOM ----- event -----> component's method ---- store.dispatch ----> store |
Hope it is clear now.
Links
- Vue.js
- Vuex
- json-server
- Source code in bahmutov/vue-vuex-todomvc