In the spirit of over-complicating things, when I wanted to collect all the links to the services on my homelab into one place, I decided I needed to write them in markdown, and have them converted on the fly into HTML by a server. Then when I couldn’t find exactly what I was after (Harp was closest) of course, I decided to write it.

Markdown
Markdown has definitely been having it’s moment over the last couple of years. It’s a simple open format mark-up language that is quite readable in it’s source form. Although it’s now very fashionable as an input for static site generators, most people will have run in to it when adding simple formatting to forum comments or on instant messaging platforms.
It supports text formatting such as bold, italic and underlining as well as links, and in some extended versions, tables and so on.
Middleware
My plan for tackling this is to have a simple Node.js/Express web server that’s serving static files from a public sub-directory. As it receives requests for each file, it checks if it’s a markdown file (which normally if served directly to a browser would trigger it to be downloaded instead of displayed). If it is markdown, it’s translated into HTML and passed to the browser.
This is easily accomplished in a simple Express server which has the concept of ‘middleware‘. This are just layers of processing that each request goes through. If a layer can deal with a request it does, otherwise it passes it off to the next layer. You’ll often see this type of pattern (usually with more layers) in an Express app, where each of the app.use declarations is another middleware layer:
const app = express();
app.use(mdParser);
app.use(express.static(publicDirectory));
In this case, mdParser is my middleware function that checks if a file is markdown, then if it is returns a HTML version of the file to the browser, or if not, just lets the request go through to the next layer. A simple version might look like this:
The actual heavy lifting of converting the markdown into HTML is done in line 20 with a library called ShowDown. There are a few of these floating around, I tried Marked first, but it didn’t immediately work how I expected without reading any documentation, so I moved on ¯\_(ツ)_/¯
Templating
This simple version works – the markdown is correctly displayed in the browser, but there’s a couple of things going on that are not great.
The first is that it’s not actually well formed HTML. If we load a markdown file containing this:
# Test.md
* A sample mark down file
It looks like this:

But if we view the page source, it’s this:
<h1 id="testmd">Test.md</h1>
<ul>
<li>A sample mark down file</li>
</ul>
No DOCTYPE, <html>, <head> etc. Since forever, browsers have been expected to deal gracefully with malformed HTML, and they generally do, but as someone who still feels bound by the ethics printed on my 1991 ACS membership certificate, I can’t accept this low standard.
There’s a second related problem I don’t like, that’s that the title of this page (displayed in the browser tab, and used if we bookmark the page) is “http://127.0.0.1:3000” instead of what I would like it to be – probably “Test”. This is not Showdown’s fault, it doesn’t really have any way of guessing what we’d like for the title.
As usual, these are a class of problem that’s long been solved, in this case with templates. Essentially what I need to do is take the generated (but not correctly formed) HTML output from Showdown, and insert it in the middle of some boilerplate HTML. Perhaps the template could look like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<main>
{{content}}
</main>
</body>
</html>
I’d put the title I wanted for the page in {{title}} and the converted markdown into {{content}}. These double curly braces are a reasonably common convention for templating.
If I load the template file (which can include all sorts of lovely CSS and JS) into templateData at start up, I can just use a string replace when I need to serve the file at request time:
if (useTemplate) {
// Replace placeholders with title and content
const title = path.basename(mdFilePath);
const templatedHtml = templateData.replace('{{title}}',
title).replace('{{content}}',
htmlContent);
res.send(templatedHtml);
} else {
res.send(htmlContent);
}
I’m just using the file name for the title here, I’ll think about how to improve that in a later installment.
Now the output looks like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test.md</title>
</head>
<body>
<main>
<h1 id="testmd">Test.md</h1>
<ul>
<li>A sample mark down file</li>
</ul>
</main>
</body>
</html>
Which, while not well indented, at least meets the HTML specification.
Done
I really enjoyed making this – it’s one of those compact sized projects you can start and finish on a Saturday between house jobs, and although small, it does address a genuine use case – if I’d found this when I was searching for something I would have used it as is.
The code’s up on github if you want a look. To make it a finished product it probably needs some hardening. Also, since I need to learn how to build Docker containers, this would be a good project for that, so stand by for a future installment.
Leave a comment