Simple Content Management with Node.js and Markdown

By Krasimir Tsonev

Recently, I released a project named Techy. It’s a flat CMS based on Node.js and Markdown. I made it because I wanted to write my articles in Markdown format and avoid the time-consuming publishing workflow which I’ve been using. This post will cover a little bit about how why I created the project, how it works and how you can use it.

The idea

The idea behind Techy is simple. Simply write Markdown files and by running the techy command in the terminal these files are converted to beautifully looking HTML pages. The final result of this article could be seen here. The home page of the blog looks like that:

techy-preview

So, we have articles. These articles have a title, date and tags attached. There are two types of pages:

  • Pages that display a list of articles (title + preface)
  • Pages that display the actual article’s content

The picture above is a preview of the first type. The list of articles is on the left side of the viewport. The sidebar on the right contains brief information about the author of the blog, the latest posts and shortcuts to pages that list articles according to their tags.

Installing Techy

Techy is a Node.js module, so you’ll need to have Node.js installed on our system. All we have to do to install techy is enter the following via the command line:

npm install -g techy

This command will set up Techy as a global command line instrument and we will be able to run the techy command.

Setup

Now, let’s create a new directory called blog. Let’s also add the necessary files and folders:

└── blog
    └── articles
        └── A.md
        └── B.md
        └── C.md
        └── ...
    └── libs
    └── partials
    └── tags
    └── all.md
    └── index.md

The articles directory will contain the blog posts. In libs we will write two custom Techy methods, but more about that in the next sections. partials will contain the code for the footer, the sidebar and two other partials. tags folder will host pages similar to index.md except that the articles inside will be filtered by specific tag. index.md is the home page and all.md shows all the written publications. On the landing page we are going to show only the latest four blog posts and we need another page with all the data.

How Techy Works

If we go to our blog directory and run techy will see that our Markdown files are converted to HTML.

└── blog
    └── articles
        └── A.html
        └── A.md
        └── B.html
        └── B.md
        └── C.html
        └── C.md
        └── ...
    └── libs
    └── partials
    └── tags
    └── themes
    └── all.html
    └── all.md
    └── index.html
    └── index.md

There is also a new folder created called themes. It contains the main HTML layout, the CSS and the JavaScript needed for the basic look of the site. There are two themes inside. Called without any parameters Techy uses the default one. So, here is the pipeline:

  • The CSS stored in themes/default/css is compiled to a file themes/default/public/styles.css
  • The JavaScript stored in themes/default/js is compiled to a file themes/default/public/scripts.js (there is no JavaScript by default)
  • Every Markdown file is converted to HTML
  • If there are any expressions they are evaluated
  • The basic layout is taken from themes/default/tpl/layouts/basic.html
  • There is a placeholder which is replaced with the content of the Markdown (point 3)
  • A file with the same name, but ending in .html is saved in the same directory

The Format for the Articles

For Techy, every file is actually an object and we may use methods like set to create properties. For example, below I set a property named somevar to 42 and then display it using get:

# That's the title of my page
<% set('somevar', 42) %>
The answer is <% get('somevar') %>.

Techy supports Yaml Front Matter, which, in a few words, means that we are able to define variables at the beginning of the file like this:

---
date: 11-01-2014
title: An article with code
preface: Curabitur consequat ligula iaculis elit aliquet tincidunt.
tags:
  - javascript
  - nodejs
---

The code above is the equivalent to:

<% set('date', '11-01-2014') %>
<% set('title', 'An article with code') %>
<% set('preface', 'Curabitur consequat ligula iaculis elit aliquet tincidunt.') %>
<% set('tags', ['javascript', 'nodejs']) %>

Techy is a flat CMS. This means that there is no database where information about the pages is stored. Instead it uses a global configuration file where it keeps the data for the articles. I personally like this solution because, when we want to change something for any post, we have to make changes in only one place.

The content of the files in the articles directory is too big to be published here but you can check it in the GitHub repository here. In general, every page has a date, title, preface and tags. In the example layout below, we are going to use these properties to generate the correct HTML content.

The Layout

By default, there is only one column of text. However, we need two columns. So, in order to make this change we need to update the main layout of the blog located in themes/default/tpl/layouts/basic.html. Let’s change the following code:

<div class="content">
   <% get('content') %>
</div>

Let’s replace that with:

<div class="content">
    <div class="row">
        <div class="col-main"><% get('content') %></div>
        <div class="col-sidebar"><% template('partials/sidebar.md') %></div>
    </div>
    <footer><% template('partials/footer.md') %></footer>
</div>

As we said above, every Markdown in Techy is an object. This object has properties and functions that can be accessed via expressions written between <% and %>. In the code above we used two functions:

  • get – this gets the content of a set variable. In our case, Techy internally uses set('content', '...') to store the content of the Markdown file.
  • template – this injects a partial. sidebar.md and footer.md do not exist yet, but we will add them in the next section.

We need to make few addition to the CSS of the site to get the two new div elements to float next to each other. By default Techy uses AbsurdJS as a CSS preprocessor. There are some basic styles written in themes/default/css. We are going to change basic.js. Here is the code needed for the layout:

'.row': {
    '-wm-bxz': 'bb',
    '&:after': {
        content: '" "', d: 'b', cl: 'b'
    },
    '.col-main': {
        '-wm-bxz': 'bb', wid: '65%', fl: 'l',
    },
    '.col-sidebar': {
        '-wm-bxz': 'bb', wid: '35%', fl: 'l',
    },
    '@media all and (max-width: 600px)': {
        '& .col-main, & .col-sidebar': {
            fl: 'n', wid: '100%'
        }
    }
}

This code may looks scary, but once you used to AbsurdJS atoms and molecules, it makes sense. The above code is compiled to:

.row, .row .col-main, .row .col-sidebar {
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
}
.row:after {
  display: block;
  clear: both;
  content: " ";
}
.row .col-main {
  width: 65%;
}
.row .col-main, .row .col-sidebar {
  float: left;
}
.row .col-sidebar {
  width: 35%;
}
@media all and (max-width: 600px) {
  .row .col-main, .row .col-sidebar {
    float: none;
    width: 100%;
  }
}

Of course, you are not tied to AbsurdJS. You may use plain CSS, LESS or SASS. Just check out the official documentation of Techy to found the necessary steps.

While I was preparing this tutorial I made some other changes to the CSS. For example, I updated the font sizes, added a background image at the top, styled the footer and so on. The source code for these changes can be found here.

Custom Techy Methods

Before we continue with building the actual content of the pages or the sidebar, let’s write two custom methods. The first one will get the Markdown files from the articles directory, will sort them by date and will return the content in the form of HTML.

// blog/lib/list.techy.js
module.exports = function(template, max) {
    template = template || 'partials/article-home.md';
    var articles = this.pages('articles', 'date');
    var html = '', to = max || articles.length;
    for(var i=0; i<to; i++) {
        var article = articles[i];
        html += this.template(template, {
            title: article.get('title'),
            preface: article.get('preface'),
            link: this.linkto(article), // linkto is also a build in function
            date: article.get('date'),
            tags: article.get('tags').join(', ')
        });
    }
    return html;
}

In order to create a custom method we need to add a file ending on .techy.js. It’s not necessary to place it in the lib folder. The file should contain the usual Node.js module which exports a function. The same function is later executed within the context of the current page. So, all of the methods that we can use in the Markdown files are available here, the only difference being that we need to call them with this. in front.

The function above accepts two parameters. The first one, template, is the skeleton within which we will put the content. We are going to use the list function on two places, so it is good to have this configurable. The default value is partials/article-home.md. The max argument defines how many articles should be included in the list. There is a built-in method called pages that gives us an access to all the Markdown files in the project. We need only those that are placed inside the articles folder. They will be sorted by the date property.

The last thing that I have to mention is the usage of the template function. We already saw this previously used in the basic layout of the theme, but here accepts a second parameter, a hash with key-value pairs. The passed keys will be available as variables within the template:

// blog/partials/article-home.md
---
noSave: true
layout: none
---

## <% get('title') %>

<small>Published on <% get('date') %>, Tags: <% get('tags') %></small>

<% get('preface') %>

<a href="<% get('link') %>">read more</a>

Notice that we are setting noSave: true and layout: none because this is a partial. We don’t want this to be saved to HTML and we don’t want to be wrapped in the basic layout.

The second method is very similar – it will filter the articles by tag.

// blog/lib/listbytag.techy.js
module.exports = function(tag) {
    var template = 'partials/article-home.md';
    var articles = this.pages('articles', 'date');
    var html = '', to = articles.length;
    for(var i=0; i<to; i++) {
        var article = articles[i];
        var tags = article.get('tags');
        if(tags && tags.indexOf(tag) >= 0) {
            html += this.template(template, {
                title: article.get('title'),
                preface: article.get('preface'),
                link: this.linkto(article),
                date: article.get('date'),
                tags: article.get('tags').join(', ')
            });
        }
    }
    return html;
}

Again, we are getting all the articles, but this time we filter them manually by the passed tag.

The Sidebar

Now that we have the list and listbytag methods we are able to make our sidebar.

// blog/partials/sidebar.md
---
layout: none
noSave: true
---

<small>About the Author:</small><br />
name: John Black<br />
interests: front-end, gaming

---

<small>Latest blog posts:</small><br />
<% list('partials/article-sidebar.md', 5) %>

---

<small>Tags:</small><br />
<a href="<% linkto('javascript') %>">JavaScript</a>
<a href="<% linkto('nodejs') %>">Node.js</a>
<a href="<% linkto('css3') %>">CSS3</a>
<a href="<% linkto('html5') %>">HTML5</a>
<a href="<% linkto('design') %>">Design</a>

This is a partial, so once again we set it to not save and have no layout. The rest is just calling the functions written in the previous section. The links to the tag pages are generated successfully by using the linkto method. This method accepts the name of a page, meaning the value in the name property set in the page.

Tags Page

This page displays the articles having javascript as a tag. The code for it is as follows:

// blog/tags/javascript.md
---
name: javascript
---
# JavaScript

<% listbytag('javascript') %>

The key moment here is the setting of the name property. That’s how the sidebar gets the correct link. The pages for the other tags are absolutely identical. Only the word javascript is replaced.

The Home and All Articles Pages

These two pages are the simplest in the whole project.

// blog/index.md
<% list(null, 4) %>

// blog/all.md
---
name: all
---
<% list() %>

The Footer

The footer contains a link to the home page and a link to the page showing all of the articles:

// blog/partials/footer.md
---
layout: none
noSave: true
---

<a href="<% get('paths').root %>"><i class="fa fa-home"></i> Home</a>
<a href="<% linkto('all') %>"><i class="fa fa-list-ul"></i> All articles</a>

The Final Compilation

To compile everything, we need to execute the techy command from within the blog folder once again. The output in the console should look like the following:

techy-output

Summary

The power of Techy is in the concept that every Markdown is an object. We are able to access it as well as define and retrieve properties on it. Having this as a base we are able to build everything and basically wire the different parts of the project together without the need of back-end code. The other benefit of such an approach is that we are serving only static HTML files, which could increase the page load performance significantly.

Source code

Demo: https://krasimir.github.io/techy-simple-blog/

Source code: https://github.com/krasimir/techy-simple-blog

Techy: https://krasimir.github.io/techy/

This article was originally published at https://krasimirtsonev.com/blog/article/No-back-end-blog-solution-with-Nodejs-and-Markdown

Previous

Replacing jQuery with Vanilla JavaScript

Slush – A Better Web App Scaffolding Tool

Next

6 thoughts on “Simple Content Management with Node.js and Markdown”

    • Node is easier to deal with. Before you can install jekyll you need ruby. For developers it doesn’t make a difference because there are all sorts of languages installed but for people that are site admins it makes a difference.

      I like absurdjs for outputing better css than other preprocessors but it’s too verbose as a preprocessor.

      • I still wonder why you would create a new ssg when there are many js ones already out there.

        https://staticsitegenerators.net

        Why not use an exist one, or fork and do pull request if it does not meet your needs. In my view there are already too many ssg’s we need less each with a larger community.

      • I make no claim of knowing about every SSG mentioned on that page, but for Jekyll, Assemble and a few other popular ones that I have tried, over-engineering is generally my biggest problem. Techy seems to be fairly barebone, perhaps it was created by one of those rare developers who are open for the possibility that their workflow is not the most innovative and superior Kung Fu in all the land…

Comments are closed.