Keystone - front-end framework for an easy web development

In this article, I’m going to talk about the 0.3.x version of Keystone. Developers are currently working on the v4.0 beta, so if you’re curious what comes with the new version check it out on Github.

Installation

Requirements

  • NodeJS
  • MongoDB

Yeoman

The easiest way to start a project in Keystone is with the use of Yeoman - don’t worry if you don’t have it installed, just open up your terminal and type:

npm install -g yo

You can read more about Yeoman on its official website.

Project directory

Now it’s time to create a project directory. To do so, write

mkdir keystone-project

and then enter the created directory

cd keystone-project

Starting the project

Now’s when Yeoman comes in handy - it will ask you a few questions and based on your needs it will generate the project structure and configuration for you.

yo keystone

The questions you are going to be asked are

What is the name of your project? Project Name
Would you like to use Jade, Nunjucks, Twig or Handlebars for templates? [jade | nunjucks | twig | hbs] nunjucks
Which CSS pre-processor would you like? [less | sass | stylus] sass
Would you like to include a Blog? No
Would you like to include an Image Gallery? No
Would you like to include a Contact Form? Yes
What would you like to call the User model? User
Enter an email address for the first Admin user: user@keystonejs.com
Enter a password for the first Admin user:
Please use a temporary password as it will be saved in plain text and change it after the first login. test
Would you like to include gulp or grunt? [gulp | grunt | none] gulp
Would you like to create a new directory for your project? No
------------------------------------------------
KeystoneJS integrates with Mandrill (from Mailchimp) for email sending.
Would you like to include Email configuration in your project? No
------------------------------------------------
Finally, would you like to include extra code comments in
your project? If you're new to Keystone, these may be helpful. No

After you’ll answer all those questions Yeoman will install all the necessary dependencies.

Running the project

The only thing left to do is start your newly created project! In your terminal type:

node keystone

If you choose to use Sass and Gulp you need to run

gulp

which will start the development server, listen to file changes, and compile styles to CSS. Your page will be available at http://localhost:3000/

When you start a project, you may encounter Error: template not found: layouts/default.html, which is caused by bad template paths defined in config file. To eliminate it open the keystone.js file which is located in the root directory of the project. Then find the 'views': 'templates/views', line and replace it with 'views': ['templates', 'templates/views'],

What is missing?

When you run the project for the first time you might expect that it will contain your typical CMS features. However, while browsing through the administration panel you’ll notice the lack of some basic features. From the start you can generate:

  • a blog,
  • a photo gallery,
  • a contact form.

As you can see, there are no dynamically generated subpages - their views, as well as routing , have to be added manually.

Adding necessary components

Given the flexibility and ease of modifying the generated CMS, it’s rather simple to add the missing dynamically generated pages.List view (based on a data model)

List view (based on a data model)

You need to start by making a new data model by creating a Page.js file in the model's directory. Then create a new list (model).

var keystone = require('keystone');
var Types = keystone.Field.Types;

/**
* Page Model
* ==========
*/

var Page = new keystone.List('Page', {
autokey: { path: 'slug', from: 'title', unique: true },
});

In the options of the list, you have to set up the automatic key generation from the title field, which has to be unique and stored in the slug field.

Page.add({
title: { type: String, required: true },
state: { type: Types.Select, options: 'draft, published', default: 'draft', index: true },
content: { type: Types.Html, wysiwyg: true, height: 400 },
});

Page.defaultColumns = 'title, state|20%, slug|20%';
Page.register();

At this moment you need to add to the list such necessary fields as title, state, and content. Set the default columns displayed in the Page.defaultColumns admin panel with their widths and record the list in Keystone. It should look like

It should look like this:

var keystone = require('keystone');
var Types = keystone.Field.Types;

/**
* Page Model
* ==========
*/

var Page = new keystone.List('Page', {
map: { name: 'title' },
autokey: { path: 'slug', from: 'title', unique: true },
});

Page.add({
title: { type: String, initial: true, required: true },
state: { type: Types.Select, options: 'draft, published', default: 'draft', index: true },
content: { type: Types.Html, wysiwyg: true, height: 400 },
});

Page.defaultColumns = 'title, state|20%, slug|20%';
Page.register();

When the model is created, and the server is running in the background (node keystone) you can add subpages in the administration panel.

Routing

Routing is located in routes/index.js (same place where middleware is set up). After the last line of routings you need to define the route that will be responsible for handling your dynamic pages:

// Setup Route Bindings
exports = module.exports = function (app) {
// Views
app.get('/', routes.views.index);
app.get('/blog/:category?', routes.views.blog);
app.get('/blog/post/:post', routes.views.post);
app.all('/contact', routes.views.contact);

app.get('/:page', routes.views.page); // <---------

// NOTE: To protect a route so that only admins can see it, use the requireUser middleware:
// app.get('/protected', middleware.requireUser, routes.views.protected);

};

Views

Right now you need to add a view and some controller that will be responsible for collecting data from the model and transferring it to the template. For this purpose, you have to create a page.js file in routes/views.

var keystone = require('keystone');

exports = module.exports = function (req, res) {

var view = new keystone.View(req, res);
var locals = res.locals;

// Set locals
locals.data = {
page: {}
};

// Load the current page
view.on('init', function (next) {

var q = keystone.list('Page').model.findOne({
state: 'published',
slug: req.params.page,
}).exec(function (err, result) {
if(!result) {
res.status(404).render('errors/404.html');
} else {
locals.data.page = result;
next(err);
}
});

});

// Render the view
view.render('page');
};

From the database, you have to get a subpage with the published status and a slug taken from the routing parameters. If such page exists you need to assign the result from the database to a variable that will be used in the template. Otherwise, you need to set the response status to 404 and display the Page Not Found information.

Template

The last thing you have to do is to create a template through which you will display the retrieved information. Create a new file in the templates/views with the name page.html.

{% extends "layouts/default.html" %}

{% set title = ( data.page.title if data.page.title else "Page" ) %}

{% block content %}
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2">
<article>
{% if not data.page %}
<h2>Invalid Page.</h2>
{% else %}
<header>
<h1>{{ data.page.title }}</h1>
</header>
<div class="page">
{{ data.page.content|safe }}
</div>
{% endif %}
</article>
</div>
</div>
</div>
{% endblock %}

You completed all the steps and created the new subpage in the administrative panel. Now in the web browser you have to enter the address that is listened by the server with the slug of created subpage (you can see it in the admin panel), e.g. http://localhost:3000/test

Advantages

Dynamic routing

Routing is dynamic and very flexible - you can fully customise it to your needs. Here’s an example:

app.get('/blog/:category?', routingViewObject);

Instead of the GET method you can use any of the the HTTP protocol method, for instance if you write

app.delete('/blog/:category', routingViewObject);

then the address would be available only when calling the query using the DELETE method. This lets you easily create a REST API.Another available, special method is all, which handles every HTTP request method.

Another available, special method is all, which handles every HTTP request method.

Administration panel

Based on data models that you made, Keystone will automatically create the admin panel that will let you add, edit, and delete database entries. These features can be disabled in the model options:

noedit Boolean
nocreate Boolean
nodelete Boolean

Additional data types

In addition to the built-in database data types (ID, String, Boolean, Number, and Date) Keystone lets you create such additional types as:

  • Email,
  • HTML,
  • Location,
  • Markdown,
  • Money,
  • Password,
  • Select,
  • Url,
  • AzureFile,
  • CloudinaryImage,
  • CloudinaryImages,
  • Embedly,
  • LocalFile,
  • S3 File,
  • Relationship.

These types are responsible for validating, formatting the data, and editing in an appropriate manner. For example, if you create a model with the HTML field data type, then in the admin panel you will have a simple WYSIWYG editor, which can be additionally configured.

Session manager

Another useful feature is the session manager, which includes password encryption. It allows you to log into the control panel or use it to authenticate visitors of your site so they can post comments or buy your products.

Application updates

Keystone has an update mechanism. It’s used for, among others, changing the data in the database if during work the model has changed and a lot of data was already entered. Updates are created once before starting the server. If they are successful, then the information about their run is stored in the database and doesn’t need repeating.

Summary

The downside of Keystone may be that it lacks some features that are pretty common in other Content Management Systems, for instance the dynamic pages. If you don’t have a lot of technical experience or want to quickly prototype a simple webpage, you may find the lack of popular plugins pretty daunting. However - in one of the GitHub discussions Keystone creators assured that they are currently working on fixing this issue. It’s a relatively young CMS (CMF) which is still growing, therefore I advise everyone to follow its development - I’m sure it’s something that I’m going to work with in the future.

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .