Simple Markdown files server that can be used as an Express middleware or standalone.
The idea is simple. You specify a folder containing Markdown text files you want to make available. The module will host these files and provide URI-like navigation to specific files based on simple routing rules.
For example, assume you have the following folder containing some Markdown files:
/home/john/guide
|---- index.md
|---- about.md
|---- walk-through/
|---- index.md
|---- act-1-town-area.md
|---- act-1-dungeon.md
By specifying /home/john/guide
as the root directory, the module can resolve the following paths to the relevant file:
/ ---> /home/john/guide/index.md (this is the "root" path)
/about ---> /home/john/guide/about.md
/walk-through/ ---> /home/john/guide/walk-through/index.md (note the trailing slash in the path)
/walk-through/act-1-dungeon ---> /home/john/guide/walk-through/act-1-dungeon.md
Note that you don't need to specify the file extension (.md) in the path as this is already assumed.
The path resolver is smart enough to handle spaces in sub folder /
file names. So, given this file /home/john/guide/"cheat codes"/"open portal.md"
(Quotes indicated here are actually not part of the
folder / file name, but just to highlight that they have spaces in the name). The following paths all resolve to the same file:
/cheat-codes/open-portal (auto sluggification)
/cheat%20codes/open%20portal (URL encoding)
/cheat codes/open portal
Note the auto "sluggification" - i.e. replacing spaces with dashes, allowing you to have clean URI navigation to the file.
The Markdown file can contain optional YAML front-matter (from start of file to first line starting with 3 dashes "---"). This is
compatible with files used in Jekyll, Octopress, & Hexo. Any arbitrary
valid YAML tag can be specified here and will be available as a JavaScript object at the markdownFile.meta
property. You can use this to
specify things such as the title of the file, or whether it is published.
When would you use this module or how is it different to Jekyll, Octopress, or Hexo?
In principle, the Markdown source files used by all of these frameworks and markdown-serve are cross compatible. However, these frameworks are static site generators. A process has to be run each time a source file is updated, new files added, or the site requires style changes to re-create the updated site. markdown-serve on the other hand brings together the flexiblity of having static source Markdown content files, but the serving of these files are dynamic as they are hosted in a Node Express server. Source content can be updated, new files added etc, and the changes will be instantly available on the website without requiring any re-generation process to run.
It is ideally suited for any small to medium websites where a full blown CMS is not required. It can be used to build mini CMS like sites (ala WordPress), blogs, or integrated into a larger Node Express application.
For example, with a personal blog website, if you have full control over the hosting environment, you can use cloud storage services like DropBox to contain your source Markdown content files. On the hosting server, you would have the DropBox daemon running to sync with your content folder, and serve up the content with markdown-serve running in an Express app. Then, regardless of which platform or device you are on, you can very easily update your website by just editing files in DropBox!
$ npm install markdown-serve
// example app.js
var express = require('express'),
mds = require('markdown-serve'),
path = require('path');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(mds.middleware({
rootDirectory: path.resolve(__dirname, 'guides'),
view: 'markdown'
}));
// example views/markdown.jade (as referenced by view parameter above):
extends layout
block content
h1= markdownFile.meta.title
#content
!= markdownFile.parseContent()
If no view and handler are specified, the module will return a JSON response of the MarkdownFile
object with HTML content available as the
markdownFile.parsedContent
property.
The preParse
option can also be set when using a view to make the HTML content available as the markdownFile.parsedContent
property. This is to support some view engines like hbs, as it doesn't support calling the
parseContent()
method in the view.
// pre-parse for use with hbs view engine
app.set('view engine', 'hbs');
app.use(mds.middleware({
rootDirectory: path.resolve(__dirname, 'guides'),
view: 'markdown',
preParse: true // setting this will parse the content and make it available as `markdownFile.parsedContent` without needing to call `parseContent()` in the view
}));
// views/markdown.hbs
<div class="container">
<h1>{{mardownFile.meta.title}}</h1>
{{{markdownFile.parsedContent}}}
<footer>
<hr />
<strong>Created:</strong> {{markdownFile.created}}
</footer>
</div>
The preParse
option can also be specified as a function. In this case, the return object from the function will be passed directly as a
view model to the view.
// preParse option specified as a function
app.use(mds.middleware({
rootDirectory: path.resolve(__dirname, 'guides'),
view: 'markdown',
preParse: function(markdownFile) {
return { title: markdownFile.meta.title, content: markdownFile.parseContent(), created: moment(markdownFile.created).format('L') };
}
}));
// views/markdown.hbs - bind directly to properties on returned object
<div class="container">
<h1>{{title}}</h1>
{{{content}}}
<footer>
<hr />
<strong>Created:</strong> {{created}}
</footer>
</div>
If you want full customization of the middleware behaviour, specify a handler
function. Note that to use this option, do not specify the
view
option as that will take precedence.
// custom handler
app.use(mds.middleware({
rootDirectory: path.resolve(__dirname, 'content'),
handler: function(markdownFile, req, res, next) {
if (req.method !== 'GET') next();
// limit access based on draft variable in front-matter
if (markdownFile.meta.draft && !req.isAuthenticated && !req.user.isAdmin) {
next();
return; // need return here
}
res.render('markdown', { title: markdownFile.meta.title, content: markdownFile.parseContent() });
}
}));
For ultimate control, define your own middleware handler.
// example app.js
var express = require('express'),
markdown = require('./routes/markdown');
var app = express();
app.get('*', markdown.handler);
// file routes/markdown.js
var server = require('markdown-serve');
exports.handler = function(req, res, next) {
if (req.method !== 'GET') next();
var markdownServer = new server.MarkdownServer( path.resolve(__dirname, 'guides') );
markdownServer.get(req.path, function(err, result) {
// result is a MarkdownFile instance
if (err) {
console.log(err);
next(); // just log error & pass it to next middleware
return; // need return here because we are inside a callback
}
// apply some custom logic based on YAML front-matter variables
if (result.meta && !result.meta.draft) {
var view = result.meta.layout || 'default';
res.render(view, { markdownFile: result });
} else {
// treat files with no front-matter / draft mode as non-existant
next();
}
});
}
Sample markdown file at guides/test.md
:
title: My awesome guide
draft: false
published: 2014-04-01 12:40:00
layout: guide
---
# My awesome guide
This is some Markdown content for [guide](http://some.co/cat.jpg)
Bullets:
- One
- Two
- Three
// file views/guide.jade
extends layout
block content
h1= markdownFile.meta.title
p.
Published: #{markdownFile.meta.published}
Created: #{markdownFile.created}
#content!= markdownFile.parseContent()
Note in the above example, the Markdown file can dictate which view is used to render the content (via the "layout" variable in the front-matter).
- Stick with all lowercase sub-folder / file names. On case-sensitive OS'es (Linux/Mac) - the resolver uses exact folder / file name match with '.md' as file extension for Markdown files.
- Avoid spaces in sub-folder / file names. The resolver can find the file quicker if it's an exact match.
- Be aware of trailing slashes in the path. This is treated as a sub-folder and actually looks for a file named "index.md" contained within it.
- Hexo - Blog framework / static site generator
- express-markdown - Very simple Express middleware for rendering Markdown files
(The MIT License)
Copyright (c) 2014 Nguyen Ly
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.