Creating a Markdown Blog with EJS & Express


This post was originally published on Ever since I posted that article, a lot of things have changed. I no longer use an EJS-Express combo blog as described below: I just use Hashnode.

I have a lot of blogs. Yep, and the thing is I like all of 'em. And each one uses a framework like Gatsby or a templating engine like Nunjucks. But it is cool to have your blog within your website. Now, my (old) website was built using Express and Express alone. I don't use any templating engines, it's just Express routes with HTML files. Having a blog within your website (all-in-one-pack) is so awesome. In this post, I'll be showing you how to create your own Markdown blog using EJS and Express!

mkdir blog
touch index.ejs
touch blog.ejs

Then, in your main Node.js script file (mine is server.js, most people have their main script at index.js), we need to configure the markdown parser, EJS and the blog directory. For that, we'll need to install a few packages.

npm i express ejs markdown-it gray-matter body-parser
  • express, the Node.js framework we're gonna be using
  • ejs, Embedded JavaScript, gonna make the blog post part easier
  • markdown-it is the markdown parser that I'm gonna be using.
  • gray-matter is a package that is gonna help me read the .md files better for extra stuff (thanks to Kumar Abhirup for introducing me to this!)

Once we've done that, we need to require all our dependencies in server.js (or index.js or whatever). We'll also be using the path module, but we don't need to install it because it is an in-built module.

// body parser
const express = require('express');
const app = express();

// if you have a public dir with static scripts and styles

var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: true }));

// path for the ejs folder
const path = require("path");

app.set("views", path.join(__dirname, "blog"));
app.set("view engine", "ejs");

// gray-matter to read the .md files better
const matter = require('gray-matter');

Then, we need to define our GET routes for the blog posts.

app.get("/blog/:article", (req, res) => {

  // read the markdown file
  const file = + '/blog/' + req.params.article + '.md');

  // use markdown-it to convert content to HTML
  var md = require("markdown-it")();
  let content = file.content;
  var result = md.render(content);

  res.render("index", {
    post: result,

What is happening here is that whenever a user visits /blog/article, the fs module looks in the blog directory for a .md file named article. (The URL of the blog post is taken from the file name minus the .md part). And the blog post markdown file should be located in the blog directory we made at first.

Now, for this to work properly, every blog post markdown file should be in the following format:

title: Title Here
description: A nice description of this post
image: A nice image representing the blog post, mainly meant for the <meta> tags

Blog post content here

The format is the reason why I used the gray-matter package. And markdown-it converts the Markdown stuff to HTML.

Also, we need a GET route to show all our blog posts (blog.ejs).

app.get("/blog", (req, res) => {
  const posts = fs.readdirSync(__dirname + '/blog').filter(file => file.endsWith('.md'));
  res.render("blog", {
    posts: posts

And finally, for our EJS files. index.ejs

    <div id="blog">
      <h1><%= title %></h1>
      <p><%= description %></p>
      <%- post %>

NOTE: An EJS variable named image (<%= image %>) is also available which is the image url mentioned in the blog post markdown file header.


    <div id="blog">
      <% for (post of posts) { %>
        <a href="/blog/<%= post.slice(0, -3) %>">
          <div class="post">
            <%= post.toUpperCase() %>  
      <% } %>

And that's it!

Of course, this is just a basic template for you to use, you can always add more HTML and style using CSS to customize according to your needs!

Any questions, tweet me on Twitter or send me an email at .

Or just ask me in the comments below.

No Comments Yet