Black Friday Sale!
Save 50% on Web Development with Go and Test with Go until Dec 3.
Both courses have lifetime access, a 30-day money back guarantee, and free updates. This is also the biggest discount I offer on the courses, and I only do it once a year around Black Friday.
Thank you for your continued support and happy coding!
Jon Calhoun
I have my application where it can render each blog post using a layout, but I need to get some additional information about each blog post to properly use the layout. To do this I am going to use frontmatter.
Frontmatter is basically just a section at the start of a markdown file that will provide some metadata. It can often be provided in a variety fo formats including YAML, TOML, and JSON. I am going to use TOML simply because it is what my current blog uses, so it is what I am familiar with right now.
The first thing I did was add some metadata to the top of each of my test blog posts. TOML frontmatter is typically indicated with the +++
characters, and then the TOML is written much like a block of code inside the characters. Below is an example of this on the how-to-boil-eggs.md
demo blog post I created.
+++
title = "How to Boil Eggs"
description = "Learn how to quickly and easily boil 6 eggs!"
date = 2024-03-03
[author]
name = "Jon Calhoun"
email = "jon@calhoun.io"
+++
# How to boil eggs
Ingredients: 6 eggs
Step 1: Boil 4 cups of water.
Step 2: Add the eggs to the water and boil for 10 minutes.
Step 3: Rinse eggs under cold water.
I added the main things I need right now, which include the author of the post and the title of the post. I also added a few pieces of information that I suspect I’ll want later on, such as a short description of the post and the date. These two will be useful when showing a list of all of my blog posts.
Next I started to look for ways to parse the frontmatter. One option is to find or create an extension for goldmark, my markdown library. Another option is to look for a standalone library that will parse the frontmatter and give me the remainder of the markdown file which I can then provide to goldmark to process.
I found libraries for both options, but ultimately decided to go with a standalone library named frontmatter because it felt easier and more intuitive to use. Your mileage may vary, so feel free to check out other options.
Now that I have a library picked I installed it.
go get github.com/adrg/frontmatter
I then proceeded to create a struct to represent a blog post. This replaced the PostData
type I used temporarily in the last part of the exercise, and it has toml
tags in it so I can more easily parse the frontmatter.
type Post struct {
Title string `toml:"title"`
Slug string `toml:"slug"`
Content template.HTML
Author Author `toml:"author"`
}
type Author struct {
Name string `toml:"name"`
Email string `toml:"email"`
}
With the type created, I updated the PostHandler
function to parse the frontmatter into a Post
.
func PostHandler(sl SlugReader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var post Post
post.Slug = r.PathValue("slug")
postMarkdown, err := sl.Read(post.Slug)
if err != nil {
// TODO: Handle different errors in the future
http.Error(w, "Post not found", http.StatusNotFound)
return
}
rest, err := frontmatter.Parse(strings.NewReader(postMarkdown), &post)
if err != nil {
http.Error(w, "Error parsing frontmatter", http.StatusInternalServerError)
return
}
// ...
}
}
At this point one thing I do want to note is that our Read
method is returning a string, and then we immediately use strings.NewReader
to turn this into an io.Reader
. What this means is that we could have likely just had our SlugReader
interface return an io.Reader
instead of a string, which I may do later, but for exercises like this I’ll often start with a string out of simplicity and familiarity, then adapt later once I know what the best fit is.
From here I proceeded to update the PostHandler
function to use the remainder of the markdown file’s contents when converting it to HTML and then I passed the post
variable with all of its data into the template.
func PostHandler(sl SlugReader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// ...
err = mdRenderer.Convert(rest, &buf)
if err != nil {
http.Error(w, "Error converting markdown", http.StatusInternalServerError)
return
}
// ... unchanged
post.Content = template.HTML(buf.String())
err = tpl.Execute(w, post)
}
}
Due to a change in how I structured the Author
field, I needed to also update my template quickly. The only part that changed was in the <div>
that contained the blog post, so I’ll show that below.
<div class="container mx-auto">
<h1 class="text-4xl font-bold text-center">{{.Title}}</h1>
{{with .Author}}
<div class="text-center mt-4">
<p class="text-gray-500">Author: <a href="mailto:{{.Email}}">{{.Name}}</a></p>
</div>
{{end}}
<div class="prose max-w-full">
{{.Content}}
</div>
</div>
My blog posts were showing the title twice at this point, so my final task was to remove the #
title piece from each blog post. This seems to be pretty standard with a lot of blog platforms and themes, but if we really wanted to keep the title heading inside of our markdown we could likely figure out a way to extend goldmark to ignore the first h1
heading in each file. I won’t be doing that, but feel free to explore goldmark a bit to see how you might do it if you want!
This article is part of the series, Exercise: Building a Blog in Go.
Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes 19 screencasts and the first few chapters from the book.
You will also receive emails from me about Go coding techniques, upcoming courses (including FREE ones), and course discounts.
Jon Calhoun is a full stack web developer who teaches about Go, web development, algorithms, and anything programming. If you haven't already, you should totally check out his Go courses.
Previously, Jon worked at several statups including co-founding EasyPost, a shipping API used by several fortune 500 companies. Prior to that Jon worked at Google, competed at world finals in programming competitions, and has been programming since he was a child.
More in this series
This post is part of the series, Exercise: Building a Blog in Go.
Spread the word
Did you find this page helpful? Let others know about it!
Sharing helps me continue to create both free and premium Go resources.
Want to discuss the article?
See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!
You can reach me via email or via twitter.
©2018 Jonathan Calhoun. All rights reserved.