Building a Blog with Ember (Part 1) From Markdown to HTML
Hi there!
What you are about to read is the result of me trying to achieve my New Year's resolution for 2019. I finally got around to do what I wanted to do for quite some time now: Build something where I can document my experience that is the daily hassle of trying to get technology to do what I want.
The first thing I want to write about is how I actually build this blog. Now I know there might already be people asking themselves "Why the hell would you build a blog in 2019, the age of static site generators?". And the answer is simple: because I wanted to get my hands dirty before starting to write about it. :)
So I started building this blog using Ember.js and decided that it could be a cool idea to do the first posts about how to build a blog with Ember. Now, before we get started, let's address the second question some people may be asking themselves right about now:
Why Ember?
In a time where you have many good options regarding static site generators that built using modern technology like React (Gatsby, React Static) or Go (Hugo) that bring a lot of awesome features out of the box for a blog, why would you use Ember to build one yourself?
I made my first experiences with Ember about 3 years ago when I got into web development. While there was a great deal of frustration since I hardly knew any JavaScript at the time and Ember traditionally has a bit of a steeper learning curve, I never stopped following what happened in the Ember community. The reason is that I really like the philosophies Ember stands for. Ember's taglines like "Convention over Configuration" or "Stability without Stagnation" always appealed to me.
So I stopped using Ember back then and spent the first months — even years — of my career getting back to the basics of understanding JavaScript a little bit better first. I also had the opportunity to write quite a lot of React lately which also helped a great deal in grasping component-based design and other concepts of web development.
But I what noticed was that after writing your 3rd or 4th React application, you start copying the same boilerplate from one project to another. Routing, state management, and the list goes on. Around the same time you could see that the Ember community was really trying to modernize the way people could write their Ember applications — Codename Ember Octane Ember Octane — and by now, many of these features are already available for use behind feature flags.
So I decided to mix two things I've been wanting to do for a while: start my own blog, give Ember another try and see how far I'd come this time.
Well, that's enough talking, let's get this baby on the road, shall we?
Setting up the Project
Since Ember Octane with all its fancy new features is still a couple of months away, we'll be using ember-cli-create to set up our new project. For this to work, make sure you have Ember-CLI globally installed:
yarn global add ember-cli
Now that we've got this down, let's start setting up the actual project, run:
yarn global add ember-cli-create && ember-cli-create
When you get prompted to choose a blueprint, select "Octance" and specify your app's name. When the installation finishes, the last thing to do is to navigate into the directory you just created and start the development server:
ember serve
And we are ready to go!
Displaying a List of Posts
First, we create a couple of markdown files that will serve us as an example to see if our code is working as expected. Inside your app's directory, run:
mkdir markdown && touch markdown/fancy-post.md && touch markdown/even-fancier-post.md
We now have a new directory markdown
containing two files. Add some content to those files, for example:
---
author: John Doe
date: 2019/02/07
slug: fancy-post
title: Ember is awesome!
---
# Insert fancy title here
When markdownum innixus tempus, est tantos telum. Non ut staminaque veneno
fecistis Finierat utrumque. Ante memor avus Achillis demptum Boeotia viscera,
timorem caelo: pararis lapidumque; tua te spumis.
## Pariter pectore do
Lorem markdownum innixus tempus, est tantos telum. Non ut staminaque veneno
fecistis Finierat utrumque. Ante memor avus Achillis demptum Boeotia viscera,
timorem caelo: pararis lapidumque; tua te spumis.
The first part, surrounded by the ---
, specifies the attributes of the document. You can use those to store meta information about the document like its author or publishing date.
What I really want to point out here is the slug
attribute. We'll be using the slug later as a unique identifier to identify which post is going to be displayed later on.
Next, let's create the route that will display our list of posts. We'll again use Ember-CLI to do that, run:
ember g route blog
This command will generate all related files for the route, like templates, tests and so on. It will also register the route in the route:
// src/router.js
Router.map(function() {
this.route('blog')
})
Now to display the posts, we need a way to resolve our markdown files and make the content available in the route's template. To achieve this, we'll make use of Ember's rich ecosystem of addons and use an addon called ember-cli-markdown-resolver. Go ahead and install it:
ember install ember-cli-markdown-resolver
When the installation has finished, restart the server. Next, we need to configure the addon and tell it where to look for our markdown files. Do this by adding the following key to the ENV
variable:
// config/environment.js
ENV['ember-cli-markdown-resolver'] = {
folders: {
'blog': 'markdown'
}
}
This tells the addon that we have a directory called markdown
containing our posts and lets us reference this location later on by using the key blog
. We can use this configuration now to make the posts available in our template. In an Ember application, the place to load data for our template is usually the model()
hook in our route. Add the following to the blog route:
// src/ui/routes/blog/route.js
import Route from '@ember/routing/route'
import { inject as service } from '@ember-decorators/service'
export default class BlogRoute extends Route {
@service markdownResolver
async model() {
const directory = await this.markdownResolver.tree('blog')
const filesSortedByDate = directory.files.sort(
(a, b) => new Date(b.attributes.date) - new Date(a.attributes.date)
)
return filesSortedByDate
}
}
Let's break down what happens here. First, inject the markdownResolver
service that the addon we just installed provides. The service allows us to get the list of our post files by using its tree
method. Calling this function will return a promise that will resolve to an object representing our directory structure. Next, we get the array of actual files out of the directory object and sort based on the date the post was published to always get the latest post on top of our list.
With our model hook in place, we can use that data in our route's template and display the list of posts:
// src/ui/routes/blog/template.hbs
<ul class="blog-listing">
{{#each model as |post|}}
<li class="blog-listing__post">
<span class="blog-listing__date">{{post.attributes.date}}</span>
<span class="blog-listing__title">
{{post.attributes.title}}
</span>
</li>
{{/each}}
</ul>
{{outlet}}
If you visit http://localhost:4200/blog
you should see the list of posts we created earlier, right on!
Displaying a single Post
Now that we have our listing, we need a place to show the contents of a single post. Our goal is to support a nested URL so that each post is available via a URL like /blog/fancy-post. Just like before start by generating a new route. Except this time we want a nested route:
ember g route blog/post
Like I said earlier, we are going to use a post's slug to identify which particular post we want to display and thus have to load. To achieve this, pass the relevant information, in that case, our slug, as a dynamic segment to the route. Let's modify the router accordingly:
// src/router.js
Router.map(function() {
- this.route('blog')
+ this.route('blog', function() {
+ this.route('post', { path: ':slug' })
+ })
})
The dynamic segment is now available in the model()
hook of our new route to load the post's content:
// src/ui/routes/blog/post/route.js
import Route from '@ember/routing/route'
import { inject as service } from '@ember-decorators/service'
export default class BlogPostRoute extends Route {
@service markdownResolver
async model({ slug }) {
return await this.markdownResolver.file('blog', slug)
}
}
Here we inject our markdownResolver
service again and use its file()
method. This returns a promise that will resolve to the contents of the markdown file which can now be displayed in our route's template.
We are now facing the problem that we have the content in that the content is available in our template but we still need to convert our markdown to valid HTML. I'm sure you already know what's coming, and you're right, there's an Ember addon that already does what we need.
We will use the Ember-CLI-Showdown addon to transform our markdown into HTML that. Let's install it:
ember install ember-cli-showdown
Restart the server and add the following code into the template to display the post:
// src/ui/routes/blog/post/template.hbs
<h1>{{model.attributes.title}}</h1>
<span class="post__meta">
<span>{{model.attributes.date}}</span>
<span>{{model.attributes.author}}</span>
</span>
{{markdown-to-html model.content}}
Since we need a way to navigate from our list to the actual posts, modify the template of our first route to link to our posts using Ember's link-to
helper. Pass it the post's slug as the dynamic to identify the post.
// src/ui/routes/blog/template.hbs
<ul class="blog-listing">
{{#each model as |post|}}
<li class="blog-listing__post">
<span class="blog-listing__date">{{post.attributes.date}}</span>
- <span class="blog-listing__title">
+ {{#link-to "blog.post" post.attributes.slug class="blog-listing__title"}}
{{post.attributes.title}}
- </span>
+ {{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
If you use these links now to navigate to one of your posts you will see the converted markdown on your page. Yay!
Refactoring the Post Listing
You may have noticed that right now we if you visit the route to display a single post you always see the list of posts above the content. The reason is that by default the content of the blog
route will be displayed on all child routes.
I don't know about you, but I'd rather just display the individual post and keep the post list on a separate route. In Ember, you can achieve this by using a so-called index
route. The corresponding index
template will be used when you visit http://localhost:4200/blog/
but will not appear on the child route where we display the individual post.
Let's do it! Generate the index route:
ember install ember-cli-showdown
Now remove everything except {{outlet}}
from
src/ui/routes/blog/template.hbs
and put it into the index template at
routes/blog/index/template.hbs
And that's it! We no longer see the post list above the post content.
Conclusion
In this post, we set up a new Ember application using the Ember Octance blueprint of ember-cli-create. We added two routes for displaying a list of all our posts and the individual posts using ember-cli-markdown and ember-cli-showdown. Now throw some CSS on it to make it look decent and you have a basic implementation of a file-driven blog built on Ember.
In the next part, we'll look at how to enhance our blog by syntax highlighting the code blocks in our posts.