A Simple CRUD Demo with Koa.js
In my previous blog post, I have introduced Koa.js, a new framework building Node.js apps, by leveraging harmony (ECMAScript 6) features of JavaScript. In this blog post, I will demonstrate how to write a web app in Koa.js with basic CRUD functionalities. The source code of the demo web app is available on Github. For the sake of the demo, the web app uses an array object as a data store for persistence, but the demo app will be re-write with MongoDB as a persistence storage and will be update on Github. The demo web app uses the following npm modules:
- Koa
- Koa-logger
- Koa-route
- co-views
- co-body
- swig
Setting up Koa.js
In order to working with Koa.js, you must install Node 0.11.9 or higher for JavaScript generator support. After installing Node 0.11.9 or higher, you can install Koa.js using npm.
npm install koa
Setting up routes for the web app
Koa is a minimalist framework and does not bundled with middleware, so that Koa itself, does not provide routing infrastructure. To use routing for Koa, you can use koa-route, which is a simple route middleware for Koa.
The code block below setting up the routing for the Koa web app.
var koa = require('koa');
var route = require('koa-route');
//create koa app
var app = koa();
// route middleware
app.use(route.get('/', list));
app.use(route.get('/todo/new', add));
app.use(route.get('/todo/:id', show));
app.use(route.get('/todo/delete/:id', remove));
app.use(route.get('/todo/edit/:id', edit));
app.use(route.post('/todo/create', create));
app.use(route.post('/todo/update', update));
Building the web app with basic CRUD
In the previous step, we have specified the routes for Koa app for handling HTTP requests. Let’s write the functions for handling the HTTP requests where we are just handling basic CRUD operations against a simple entity. For the simplicity, we are just using a JavaScript array as the data store
var todos = [];
The code block below implements the List function for handling the requests for home page, route for ‘/’.
function *list() {
this.body = yield render('index', { todos: todos });
}
The List function denotes with * since we are using a generator function where we are rendering the view with yield keyword. We are rendering the views with Swig templates. The implementation of render is provided below:
//Rendering the views with Swig template engine
var render= views(__dirname + '/views',
{ map: { html: 'swig' }});
Unlike Express.js, we are not specifying the Request and Response objects for the parameters for routing functions. Koa provides a Context object, that encapsulates node's request and response objects into a single object, which can be access through the this keyword. The Context object also provides alias methods for accessing the methods of Request and Response objects. The above List function uses this.body which is a shortcut to Response object’s body.
this.body = yield render('index', { todos: todos });
The code block below provides the implementation for Index view, where we use Swig template view engine for rendering view.
{% extends 'layout.html' %}
{% block title %}Todo Tasks{% endblock %}
{% block content %}
<h1>Todo List</h1>
<p>You have <strong>{{ todos.length }}</strong>
Todo Items!</p>
<p><a href="/todo/new">Create a ToDo</a></p>
<ul id="todos">
{% for todo in todos %}
<li>
<strong>{{ todo.name }}</strong>||
<a href="/todo/{{ todo.id }}">View Details</a> ||
<a href="/todo/edit/{{ todo.id }}">Edit</a> ||
<a href="/todo/delete/{{ todo.id }}">Delete</a>
</li>
{% endfor %}
</ul>
{% endblock %}
The code block blow provides the complete implementation of Index.js which is the start-up file for our Noe.js application.
**
* Module dependencies.
*/
var logger = require('koa-logger');
var route = require('koa-route');
var views = require('co-views');
var parse = require('co-body');
var koa = require('koa');
var app = koa();
// "data store"
var todos = []; //To Do : DB change to MongoDB
// middleware
app.use(logger());
// route middleware
app.use(route.get('/', list));
app.use(route.get('/todo/new', add));
app.use(route.get('/todo/:id', show));
app.use(route.get('/todo/delete/:id', remove));
app.use(route.get('/todo/edit/:id', edit));
app.use(route.post('/todo/create', create));
app.use(route.post('/todo/update', update));
//Specifying Swig view engine
var render= views(__dirname + '/views',
{ map: { html: 'swig' }});
// route definitions
/**
* Todo item List.
*/
function *list() {
this.body = yield render('index', { todos: todos });
}
/**
* Form for create new todo item.
*/
function *add() {
this.body = yield render('new');
}
/**
* Form for edit a todo items.
*/
function *edit(id) {
var todo = todos[id];
if (!todo) this.throw(404, 'invalid todo id');
this.body = yield render('edit', { todo: todo });
}
/**
* Show details of a todo item.
*/
function *show(id) {
var todo = todos[id];
if (!todo) this.throw(404, 'invalid todo id');
this.body = yield render('show', { todo: todo });
}
/**
* Delete a todo item
*/
function *remove(id) {
var todo = todos[id];
if (!todo) this.throw(404, 'invalid todo id');
todos.splice(id,1);
//Changing the Id for working with index
for (var i = 0; i < todos.length; i++)
{
todos[i].id=i;
}
this.redirect('/');
}
/**
* Create a todo item into the data store.
*/
function *create() {
var todo = yield parse(this);
todo.created_on = new Date;
todo.updated_on = new Date;
var id = todos.push(todo);
todo.id = id-1;//Id with index of the array
this.redirect('/');
}
/**
* Update an existing todo item.
*/
function *update() {
var todo = yield parse(this);
var index=todo.id;
todos[index].name=todo.name;
todos[index].description=todo.description;
todos[index].updated_on = new Date;
this.redirect('/');
}
// http server listening
app.listen(3000);
console.log('listening on port 3000');
In the HTTP Post operations, we are parsing the form fields values by using the module co-body which parse the request bodies with co.
Source Code
The source code of the demo we app is published on github at https://github.com/shijuvar/Koa-CRUD
You can follow me on Twitter @shijucv