Getting started
Table of Contents
- Installation
- Starting a project
- Deployment
- Describing the data
- Querying the data
- Rendering pages
- Users and permissions
- Embedded types
- Adding forms
- Going it alone
Installation
Install the most recent stable version of node, then clone kanso from GitHub. Fetch the relevant submodules by doing the following in the cloned directory:
git submodule init
git submodule update
You are then ready to install:
make && sudo make install
Using NPM
If you already have node.js installed, and you're using npm (Node Package Manager), then you can install by simply doing the following:
sudo npm install -g kanso
Why does Kanso need node.js?
Node.js is used for the command-line tools only. With Kanso, the end result is a pure CouchApp you can host using CouchDB alone. Using node to write the associated tools allows us to do some powerful things by interpreting the JavaScript of your application.
CouchDB version
As of release 0.0.7, Kanso only supports CouchDB 1.1.0 or higher. This is due to a number of fixes in CouchDB 1.1.0 for CommonJS modules which make Kanso faster and helps to keep the framework code clean. If you're running an older version of CouchDB you'll want to upgrade it before continuing with this guide.
Starting a project
For this tutorial we'll be making a fairly typical blog, with posts, comments and users. Some familiarity with CouchDB is expected, but I'll try to explain the concepts as we go.
To create a new project skeleton, enter the following command:
kanso create myblog
This creates a number of files and directories representing a basic project structure. We'll look more closely at the generated files later, but for a brief overview, the directories fall into the following categories:
myblog
|- lib CommonJS modules which define your app
|- static Static files such as jQuery and CSS
|- templates HTML templates used by the app
|- kanso.json Project configuration
This structure is a merely a guide and you are free to place files wherever it makes sense, provided you update the configuration settings in kanso.json.
Deployment
It might seem early in the tutorial to start talking about deployment, but since your app needs to be hosted by CouchDB to run, we're going to cover it now.
The first thing to mention is that Kanso apps are just CouchApps. This means they can be hosted directly from your CouchDB instance, without any additional tier or services.
For the rest of this tutorial we're going to assume you have CouchDB running on http://localhost:5984 (the default settings). If you haven't got a local copy of CouchDB running, go do that now.
Pushing
The push
command uploads your app to a CouchDB database. You can find
help on any of the commands used in this tutorial by typing kanso help
.
Let's push the new project to your CouchDB instance, and check that everthing works. To do this, enter the following command from your project directory:
kanso push http://localhost:5984/myblog
This creates a new database called 'myblog' and uploads your app to it. If you now visit http://localhost:5984/myblog/_design/myblog/_rewrite/, you should see the following welcome page:
This is the location to use when testing your app. Don't worry about the ugly URL, we can fix this later using virtual hosts.
Describing the data
Now we've got our new project up and running, let's think about the data structures this project requires. Because CouchDB is schemaless, we can make lots of changes as we go without having to worry about doing ALTER TABLE.
The only problem is, it can quickly get complicated when manually validating documents and checking permissions. Thankfully, kanso provides a powerful document validation and permissions tool in the form of Type definitions.
Creating types
In the skeleton project we created earlier, there's a file called
lib/types.js
. This is the conventional place to export types.
Remember, the files in the lib
directory are
CommonJS modules.
This means anything added to the exports
object in this file is
made visible to other modules.
Let's create a type for describing blog posts. Add the following to
lib/types.js
.
var Type = require('kanso/types').Type,
fields = require('kanso/fields'),
widgets = require('kanso/widgets');
exports.blogpost = new Type('blogpost', {
fields: {
created: fields.createdTime(),
title: fields.string(),
text: fields.string({
widget: widgets.textarea({cols: 40, rows: 10})
})
}
});
That should be fairly self-explanatory.
The first argument to the Type
constructor is the type name
which must be unique, the second argument is an object describing fields
and other options.
Now we've added a basic type, let's push these changes to the server:
The Admin App
As a useful way to play with data types, kanso provides a basic admin app. You can push this app to the same database you pushed the 'myblog' project to by using the following command:
kanso pushadmin http://localhost:5984/myblog
If you now visit http://localhost:5984/myblog/_design/admin/_rewrite/ you should see the following page:
Because you can have multiple apps running on a single database you will be presented with a list of available applications. Click the 'myblog' app.
Here, we are presented with a list of types. Currently, we just have the one 'blogpost' type. Clicking on this will show you a list of existing blog posts:
Of course, we haven't added any yet. Let's try adding one now. Click the 'Add blogpost' link.
As you can see, the admin app has read the field settings we defined earlier
and presented us with a sensible form for adding blog posts. You'll notice the
createdTime
field is missing. This is because it's automatically
populated with the current time.
Create a blog post by filling out the form with some test data and clicking the create button.
As you can see, kanso has automatically populated the 'type' and 'created' fields. The 'type' field is automatically added, and reserved for use by kanso so we can identify the definition to validate against.
Querying the data
In CouchDB, you query your documents using map / reduce functions called 'views'. If you've never written a CouchDB view before, you can read up on it in the Definitive Guide.
Adding a view to a kanso app couldn't be simpler. Just open up the
lib/views.js
file and add the following example view:
exports.blogposts_by_created = {
map: function (doc) {
if (doc.type === 'blogpost') {
emit(doc.created, doc.title);
}
}
};
Push the app, then revisit the admin interface at http://localhost:5984/myblog/_design/admin/_rewrite/myblog you'll see the view we created now appears:
Clicking on the view shows the current results. You should see the document we created earlier keyed by its created at timestamp:
Clicking on a row will take you to its corresponding document.
Rendering pages
In CouchDB, rendering custom representations of your data is achieved using List and Show functions. Kanso is no different, although it will also run these functions client-side wherever possible, giving users a more responsive interface while also providing a fallback for search-engines.
Writing code in this way can be very efficient and shows where you might be missing non-js support. However, at first it might seem a little odd. Work through the examples and bear in mind that all list and show functions could be run client-side or server-side at any time!
List functions
List functions format the results of a view. We're going to use a list
function to show a list of blog posts on the homepage, ordered by the
date they were created. Add the following to lib/lists.js
:
var templates = require('kanso/templates');
exports.homepage = function (head, req) {
start({code: 200, headers: {'Content-Type': 'text/html'}});
// fetch all the rows
var row, rows = [];
while (row = getRow()) {
rows.push(row);
}
// generate the markup for a list of blog posts
var content = templates.render('blogposts.html', req, {
rows: rows
});
if (req.client) {
// being run client-side, update the current page
$('#content').html(content);
document.title = 'MyBlog';
}
else {
// being run server-side, return a complete rendered page
return templates.render('base.html', req, {
content: content,
title: 'MyBlog'
});
}
};
For simplicity, we're pre-fetching all the rows at once, on large views you'll want to return content for each row before fetching the next.
You'll notice this list function detects if its running client-side by checking the client property on the request object. When run client-side, it will update the DOM instead of returning a new HTML document.
Since this is such a common pattern when writing Kanso apps, you can use a convenient short-hand of returning an object with title and content properties, Kanso will then do the appropriate thing for the current environment:
var templates = require('kanso/templates');
exports.homepage = function (head, req) {
start({code: 200, headers: {'Content-Type': 'text/html'}});
// fetch all the rows
var row, rows = [];
while (row = getRow()) {
rows.push(row);
}
// generate the markup for a list of blog posts
var content = templates.render('blogposts.html', req, {
rows: rows
});
return {title: 'MyBlog', content: content};
};
We referenced two templates in the first example: 'base.html' and
'blogposts.html'. The first was created as part of the project skeleton,
but the second we need to create ourselves.
Create a new template at templates/blogposts.html
with the
following content:
<h1>My Blog</h1>
{?rows}
<ul>
{#rows}
<li><a href="{baseURL}/{id}">{value}</a></li>
{/rows}
</ul>
{:else}
<p>No blog posts</p>
{/rows}
For more information on the template format used by kanso, see the Dust website. Basically, this will render a list of blog post titles.
One thing worth noting is the use of {baseURL} in the template. This variable is automatically made available to templates by kanso, and refers to the current 'root' level URL. That would currently mean '/myblog/_design/myblog/_rewrite', but that may change if you use a virtual host in the future. For now, be sure to prefix all application URLs with the base URL.
Rewrites
Next, we need to hook this view up to a URL. CouchDB uses
rewrites
to point arbitrary URLs at CouchDB functions and again, kanso is no exception.
Let's change the root URL from the welcome page to our new list of blog posts.
Edit lib/rewrites.js
to look like the following:
module.exports = [
{from: '/static/*', to: 'static/*'},
{from: '/', to: '_list/homepage/blogposts_by_created'},
{from: '*', to: '_show/not_found'}
];
This uses our new list function in combination with the view query we created earlier. Push the app, then open it in your browser: http://localhost:5984/myblog/_design/myblog/_rewrite/.
Of course, clicking on the blog post will give a 404 Not Found error, since we've not implemented the view for a single blog post yet. Let's do that now.
Show functions
Show functions are like list functions, only they display a single document
rather than the results of a view. We're going to create a show function
for our blog posts, open up lib/shows.js
and take a look.
Currently, it will contain the welcome page we recently unhooked from our
rewrite rules. You can now remove that, and replace it with the following:
var templates = require('kanso/templates');
exports.blogpost = function (doc, req) {
return {
title: doc.title,
content: templates.render('blogpost.html', req, doc)
};
};
You'll notice we're using the short-hand method used in the list function
earlier, by returning an object with title and content properties.
Next we need to add the blogpost.html
template:
<h1>{title}</h1>
<p>{text}</p>
And update the rewrite rules:
module.exports = [
{from: '/static/*', to: 'static/*'},
{from: '/', to: '_list/homepage/blogposts_by_created'},
{from: '/:id', to: '_show/blogpost/:id'},
{from: '*', to: '_show/not_found'}
];
Push the app and try clicking the blog post link now. You should see a page similar to the following:
If you check your JavaScript console in Chrome or Firebug, you should notice that clicking on the blog post link on the homepage causes the next request to be handled client-side:
However, turning off JavaScript should provide you with the same experience. Just with your CouchDB server doing the work of rending pages.
As your sites become more complex you may want to make additional requests to the server when a list or show function is run client-side. This means we could show all blogposts with the same tag in a side-bar, or perhaps show links to related posts. These features would then be available only to people with JavaScript turned on, and browsers without JavaScript will fall-back to the core content you see now.
Users and permissions
Currently, anyone can add a blog post to the site, even if they're not logged-in. This probably isn't ideal, so let's look at protecting the ability to add blog posts.
CouchDB provides its own users and roles system, which defines who has access to which database. Kanso can also use this to authorize or reject changes to the database on a document-level. The great thing about this is that our authentication system is already set-up and ready to go!
validate_doc_update
CouchDB authorizes changes to a document using the validate_doc_update function defined in your app. This function accepts the new document, the previous version of the document (if available), and the user's details as arguments and throws an Error if the update should be rejected.
Kanso also supports this system as the underlying way to authorize changes. However, this method is quite low-level. The simplicity of this approach is both its attraction and its downfall. With complex applications comprising of many types, each with it's own validation and authorization requirements, your validate_doc_update function quickly becomes unwieldy.
Kanso allows you to mix-and-match, by providing powerful tools on a type or even field-level, while still supporting the simplicity of the validate_doc_update approach.
If you look at lib/validate.js
in the project skeleton we
generated, you'll notice kanso has already added a
validate_doc_update function which just calls the kanso function
types.validate_doc_update
. This runs the permissions
defined in your app's type and field definitions, so it's
important not to remove this line if you're using this feature.
var types = require('kanso/types'),
app_types = require('./types');
module.exports = function (newDoc, oldDoc, userCtx) {
types.validate_doc_update(app_types, newDoc, oldDoc, userCtx);
};
If you also want to add permissions directly into this validate_doc_update function, you can do that too.
End of the admin-party
By default, CouchDB has no users and everyone has the special "_admin" role. This makes playing around with CouchDB (and Kanso) easy when you first install, but obviously isn't very secure. If you haven't done so already, put an end to the admin party by visiting the Futon admin interface and adding a new admin user:
Type-level permissions
Currently, we want to protect the ability to add, remove and update the blogpost type. To do this we simply update our type definition to include a permissions section:
var Type = require('kanso/types').Type,
fields = require('kanso/fields'),
widgets = require('kanso/widgets'),
permissions = require('kanso/permissions');
exports.blogpost = new Type('blogpost', {
permissions: {
add: permissions.hasRole('_admin'),
update: permissions.hasRole('_admin'),
remove: permissions.hasRole('_admin')
},
fields: {
created: fields.createdTime(),
title: fields.string(),
text: fields.string({
widget: widgets.textarea({cols: 40, rows: 10})
})
}
});
Push the app (you may now be prompted for a username and password), if you then logout and go to the blogposts page in the admin app. When you try to delete a blogpost, you should see the following:
The same should happen when editing or adding new blog posts. Logging back in as an admin user should allow you to complete all of these operations again.
A short note on pushing as a user: if you include a username or both a username and password in the database URL, these credentials will be used to push the app.
kanso push http://user:[email protected]:5984/dbname
kanso push http://[email protected]:5984/dbname
You may not want to expose the password in your shell history so I'd recommend just putting the username in the URL, and kanso will know to prompt you for a password before attempting to push.
Embedded types
Now we move on to one of Kanso's more advanced features: Embedded types.
Beacuse it often makes sense to 'de-normalize' your data in CouchDB you'll often find yourself embedding one document directly into another. Any Type system that's going to work properly will also need to support this technique.
This can get quite tricky once you consider the permissions and validation requirements involved. Consider a situation where you'd like to embed comments on a blog post document. One user may be allowed to edit a blogpost, while another may only be allowed to comment on it. However, both users need to be able to update the document itself, since the comments are embedded within the blogpost.
Using embedded types, Kanso helps you handle these situations much more easily and define exactly which parts of a document can be edited by a user.
Whether or not embedding one type within another document is a good idea often depends on the specific circumstances. Comments on blog posts for example, may be best added separately to the blog post document if you expect there to be a lot of commenting activity, otherwise you run the risk of conflicts, and CouchDB will have to write the whole list of comments every time you add a new one.
For the sake of this example however, we're going to assume comments are very infrequent and that we prefer to make them available on the blog post document itself.
Creating the comment type
Before we can embed comments within the blogpost type, we need to define
the comment type itself. Add the following to lib/types.js
,
BEFORE the blogpost type:
exports.comment = new Type('comment', {
permissions: {
add: permissions.loggedIn(),
update: permissions.usernameMatchesField('creator'),
remove: permissions.usernameMatchesField('creator')
},
fields: {
creator: fields.creator(),
text: fields.string({
widget: widgets.textarea({cols: 40, rows: 10})
})
}
});
You'll notice we're using some new permissions for this type,
first loggedIn
simply checks the user is logged in (not
anonymous) before being allowed to add a comment. Then,
usernameMatchesField
checks that the current user is the same
as the user defined in the creator field. This stops users from editing or
deleting other users comments.
The creator
field is also new, and set's itself to the current
user's name when a new comment is added. After creation, this field becomes
uneditable.
Embedding a type
To embed a list of comments in a blogpost, update the blogpost type definition to include the following comments field:
exports.blogpost = new Type('blogpost', {
permissions: {
add: permissions.hasRole('_admin'),
update: permissions.loggedIn(),
remove: permissions.hasRole('_admin')
},
fields: {
created: fields.createdTime(),
title: fields.string({
permissions: {
update: permissions.hasRole('_admin')
}
}),
text: fields.string({
widget: widgets.textarea({cols: 40, rows: 10}),
permissions: {
update: permissions.hasRole('_admin')
}
}),
comments: fields.embedList({
type: exports.comment,
required: false
})
}
});
You'll notice we've changed the permissions a bit. Previously the type had add, update and remove permissions at the top-level which all checked for the '_admin' role. That meant you couldn't create, delete or make any changes to a blogpost without being a CouchDB admin.
Since we're embedding comments within the blogpost document, we need to give update access to any logged-in user, so they can add comments directly into it. However, we still don't want them to change the title or text of the blogpost, so we've added field-level permissions to protect those.
The embedList
field will use the permissions of the comment Type
definition, meaning any logged-in user can add a comment, but you can only edit or
delete comments you created.
This is an example of the flexibility of the Kanso Types and permissions system. De-normalizing your data often means you'll need some fine-grained permissions to protect specific parts of a document. Because Kanso was written with CouchDB in mind, we can design specifically for these kinds of problems.
Push the app, and visit the admin page for adding blogposts: http://localhost:5984/myblog/_design/admin/_rewrite/myblog/types/blogpost/add
There is now an add a comment button, clicking on this will open a modal dialog for embedding a comment.
Continue to add a new blog post with some example comments, rembering that you'll need to be logged in as an admin user to add new blog posts.
Updating templates
Let's update the templates to handle the newly embedded comments.
Edit templates/blogpost.html
to look like the following:
<h1>{title}</h1>
<p>{text}</p>
{?comments}
<h2>Comments</h2>
<ul>
{#comments}
<li>
<strong>{creator} says: </strong>
{text}
</li>
{/comments}
</ul>
{/comments}
Push the app, and visit it's home page: http://localhost:5984/myblog/_design/myblog/_rewrite/
You should see the new blog post you created listed on the homepage. Clicking on it should show it using the new template, complete with the comments you added earlier:
Adding forms
So far, we've been using the Kanso admin app to add documents, but let's assume we want to write our own interface and forms. Kanso provides some useful form-rendering code (also used by the admin app) which allows you to quickly and easily create forms based on Type definitions.
First, we're going to create a form for adding new blog posts.
Add the following to the end of templates/blogposts.html
:
<p><a href="{baseURL}/add">Add new</a></p>
Then update lib/rewrites.js
to look like this:
module.exports = [
{from: '/static/*', to: 'static/*'},
{from: '/', to: '_list/homepage/blogposts_by_created'},
{from: '/add', to: '_show/add_blogpost'},
{from: '/:id', to: '_show/blogpost/:id'},
{from: '*', to: '_show/not_found'}
];
Let's create a new show function to display the form. Add the following to
lib/shows.js
:
exports.add_blogpost = function (doc, req) {
// render the markup for a blog post form
var content = templates.render('blogpost_form.html', req, {
form_title: 'Add new blogpost'
});
return {title: 'Add new blogpost', content: content};
};
And add a new template for the page at templates/blogpost_form.html
.
<h1>{form_title}</h1>
Push the app, and visit it's home page: http://localhost:5984/myblog/_design/myblog/_rewrite/
Clicking 'Add new' should display the following page:
So far so good. Let's add a form to the page. In lib/shows.js
add the following new modules to the requires at the top:
var templates = require('kanso/templates'),
forms = require('kanso/forms'),
types = require('./types');
Then update exports.add_blogpost
to look like this:
exports.add_blogpost = function (doc, req) {
var form = new forms.Form(types.blogpost, null, {
exclude: ['created', 'comments']
});
// render the markup for a blog post form
var content = templates.render('blogpost_form.html', req, {
form_title: 'Add new blogpost',
form: form.toHTML(req)
});
return {title: 'Add new blogpost', content: content};
};
You'll notice we're using the new forms
module to construct a
form object using the blogpost Type definition we created earlier. We are
excluding the created and comments fields, and just displaying the fields we need.
Next we render the form as HTML using form.toHTML(req)
, and pass it
to the template. In templates/blogpost_form.html
we need to add the
following:
<h1>{form_title}</h1>
<form method="POST" action="">
<table>
{form|s}
</table>
<input type="submit" value="Add" />
</form>
Push the app, and visit the add blogpost page: http://localhost:5984/myblog/_design/myblog/_rewrite/add
Great, but if you try submitting the form, nothing happens! Thats because we need to add an update function for saving changes to a document.
Update functions
Add the following to lib/updates.js
:
var templates = require('kanso/templates'),
forms = require('kanso/forms'),
utils = require('kanso/utils'),
types = require('./types');
exports.add_blogpost = function (doc, req) {
var form = new forms.Form(types.blogpost, null, {
exclude: ['created', 'comments']
});
// parse the request data and check validation and permission functions
form.validate(req);
if (form.isValid()) {
// the form is valid, save the document and redirect to the new page
return [form.values, utils.redirect(req, '/' + form.values._id)];
}
else {
// the form is not valid, so render it again with error messages
var content = templates.render('blogpost_form.html', req, {
form_title: 'Add new blogpost',
form: form.toHTML(req)
});
// return null as the first argument so the document isn't saved
return [null, {content: content, title: 'Add new blogpost'}];
}
};
Update functions expect an array as the return value, the first argument is the document to create or update, the second argument is the response to send to the client. This function should look similar to the show function we created for displaying the add blog post form, only with the addition of form validation.
After creating the form, we call form.validate
with the request
object. This will populate the form with the request data and any relevant error
messages if the data is invalid.
We then check the validity of the form and either reject or accept the new document. Note, that this is mostly to provide a nice response to the user and is not required for security. Ultimately, an unacceptable document would be rejected by validate_doc_update, even if this function were to accept it.
Next, we need to hook this update function up to a URL using rewrites. Edit
lib/rewrites.js
to look like the following:
module.exports = [
{from: '/static/*', to: 'static/*'},
{from: '/', to: '_list/homepage/blogposts_by_created'},
{from: '/add', to: '_update/add_blogpost', method: 'POST'},
{from: '/add', to: '_show/add_blogpost'},
{from: '/:id', to: '_show/blogpost/:id'},
{from: '*', to: '_show/not_found'}
];
We've added a new rewrite for /add
, only this time we specify a HTTP
method to match. This means when we do GET /add
we'll run the show
function, and when we do POST /add
we'll run the update function.
Let's try submitting the form now. Be sure to login as an admin user using either futon or the Kanso admin app (on the same hostname you're using with the app, no good logging in on localhost if your accessing the app on 127.0.0.1).
Push the app, and visit the add blogpost page: http://localhost:5984/myblog/_design/myblog/_rewrite/add
After submitting the form you should be redirected to the list of blogposts and you should see your new blogpost added to the end.
Going it alone
Congratulations on finishing your first (basic) Kanso app! It's time for you to strike out on your own, and explore the possibilities of a whole new way to write web-apps. If you create something cool, please share it on the Kanso wiki.
You can report issues using the GitHub issues page.
Don't forget to check out the Docs and other available Guides.