Building a Blog with Go, Tailwind CSS, Cosmic & Deploy to Heroku with GitHub Actions
Tags: go, cosmiccms, headlesscms, tailwindcss
Published: 2020-09-17 16:16:06

---
title: Building a Blog with Go, Tailwind CSS, Cosmic & Deploy to Heroku with GitHub Actions
published: true
description: In this walkthrough, I'm going to show you how I built a blog in Go Lang with content powered by the Cosmic headless CMS.
tags: golang, cosmiccms, headlesscms, tailwindcss
cover_image: https://imgix.cosmicjs.com/313ea1e0-f8ec-11ea-95ce-8dd2bc6bf45e-go-cosmic-cms.jpg?w=1000
canonical_url: https://www.cosmicjs.com/articles/building-a-blog-with-go-cosmic-headless-cms-deploy-to-heroku-with-github-actions
---

In this walkthrough, I'm going to show you how I built a blog in [Go](https://golang.org/) with content powered by the [Cosmic headless CMS](https://www.cosmicjs.com/headless-cms). I'll also talk about styling it using [Tailwind CSS](https://tailwindcss.com/) and lastly, I'll show you how you can utilize [GitHub Actions](https://docs.github.com/en/actions) to deploy your Go-based applications to [Heroku](https://www.heroku.com/). Let's get started.

## TL;DR
[View Demo](https://www.cosmicjs.com/apps/go-cosmic-blog)
[View the full source code on GitHub](https://github.com/cosmicjs/go-cosmic-blog)
[Install the App](https://www.cosmicjs.com/apps/go-cosmic-blog)


## Fundamentals
Every blog consists of at least two components:
- List Page (Blog roll)
- Individual Blog Post Page

Go is an excellent back-end language to build an optimized and highly concurrent application. In order to show the potential of Go to our [Cosmic Community](https://www.cosmicjs.com/community), I'll be building a simple blog application. To get started, we'll need to populate content in Cosmic.


## Blog Data
In order to provide the blog post data, I installed the [Simple Blog](https://www.cosmicjs.com/apps/simple-blog) app on my Cosmic Account Bucket and voila my blog CMS and API service was ready within seconds.

But if you don't want to install an App and want to try it on your own, you can follow these steps.

1. [Log in](https://app.cosmicjs.com/login) and Create a new Bucket in Cosmic
2. Create an Object Type `Posts` in that bucket.
3. Add following Metafields:
 - Hero -> Image/File Metafield
 - Description -> Textarea Metafield


## Go App Code
This Go Blog app has three major components

1. Domain
2. Routes
3. Templates
Now, I will go through each of them separately.


### Domain
Go is a statically typed language, meaning, in order to work with Go, we need to create `structs` to process the data from the Cosmic API. In our blog application, I will be working with one data model `Post`. Our `Post` struct consists of `Title`, `Slug`, `Content`, `CreatedAt` and `Metadata`.
```go
// Post is a representation of post object
type Post struct {
  Title     string
  Slug      string
  Content   template.HTML
  CreatedAt time.Time `json:"created_at"`
  Metadata  Metadata
}
```
As you see, I added `json:"created_at"` to our `CreatedAt` attribute, which I'll explain later on why it is required. Also, you see `Metadata` is a linked struct which contains hero and `description` Metafields from Cosmic CMS.
```go
// Metadata is a representation of metadata object
type Metadata struct {
  Hero        Image
  Description string
}

// Image is a object of URL & ImgixURL
type Image struct {
  URL      string
  ImgixURL string `json:"imgix_url"`
}
```
As this point, our Domain is completed. But there are few more structs needed as our Cosmic API returns different JSON responses from the multiple Objects and single Object endpoints. Below is the full code for `domain.go` file which I put in `src/domain` directory for clean code structure.
```go
package domain

import (
  "html/template"
  "time"
)

// ObjectsData is a array of objects from Cosmic API
type ObjectsData struct {
  Objects []Post
}

// ObjectData is a object from Cosmic API
type ObjectData struct {
  Object Post
}

// Post is a representation of post object
type Post struct {
  Title     string
  Slug      string
  Content   template.HTML
  CreatedAt time.Time `json:"created_at"`
  Metadata  Metadata
}

// Metadata is a representation of metadata object
type Metadata struct {
  Hero        Image
  Description string
}

// Image is a object of URL & ImgixURL
type Image struct {
  URL      string
  ImgixURL string `json:"imgix_url"`
}
```
As mentioned before, I added JSON field name from API response in front of some attributes i.e `json:"created_at"`. It is because we will be using JSON Unmarshal in Go to parse and map the JSON response to our domain models. As it doesn't work with fields having underscore in it. So, we need to explicitly tell the name of the fields.

Now, I'll move to building routes and controllers for our application.


### Routes (Controllers)
In every blog application, there are two major pages. One that will show the list of all blog posts and another which will show the single blog post. Before I'll implement controller actions, I'll show you how I set up the simple Go web server and routes using `net/http` and `gorilla/mux modules`. Below is the code for `app.go` file which is responsible for handling our routes and HTTP server.
```go
package main

import (
  "fmt"
  "go-cosmic-blog/src/handlers"
  "go-cosmic-blog/src/utils"
  "log"
  "net/http"

  "github.com/gorilla/mux"
  "github.com/joho/godotenv"
)

func main() {
r := mux.NewRouter()

// Choose the folder to serve
staticDir := "/static/"

// Create the route
r.PathPrefix(staticDir).Handler(http.StripPrefix(staticDir, http.FileServer(http.Dir("."+staticDir))))

r.HandleFunc("/", handlers.Index).Methods("GET")
r.HandleFunc("/{slug}", handlers.Single).Methods("GET")

if err := godotenv.Load(); err != nil {
  log.Println("Error loading .env file")
}

port := utils.GetPortEnv()

fmt.Println("Starting server at port", port)
  if err := http.ListenAndServe(port, r); err != nil {
    log.Fatal(err)
  }
}
```
As you see I also included `joho/godotenv` and two local modules `go-cosmic-blog/src/handlers` and `go-cosmic-blog/src/utils`. `joho/godotenv` will handle the environment variables from `.env` file and `go-cosmic-blog/src/utils` have helper functions for our application.

You can look at `.env.example` file [here](https://github.com/cosmicjs/go-cosmic-blog/blob/master/.env.example) and `src/utils/helpers.go` [here](https://github.com/cosmicjs/go-cosmic-blog/blob/master/src/utils/helpers.go).

Now, I'll show you the creation of Index Handler. To keep everything structured, I created `index.go` file in `src/handlers/` directory. This index handler will fetch the blog posts from Cosmic CMS, parse and map it to our domain models and then send that data to HTML templates. So, our skeleton for Index handler is as bellow:

```go
package handlers // our local package name

// Index handler to serve / route
func Index(w http.ResponseWriter, r *http.Request) {

    // Fetch list of objects from Cosmic CMS

    // Parse and map it to Domain Models

    // send that data to our templates
}
```
In order to fetch the post objects, I called the Cosmic CMS API endpoint. As this endpoint requires basic parameters like `BUCKET_SLUG` and `READ_KEY`. I put them in the `.env` so that we don't need to hard code them. Below code is getting these parameters from `.env` and then fetching post Objects from Cosmic CMS API.
```go
if ok := utils.CheckIfEnvExists("BUCKET_SLUG"); !ok {
    http.Error(w, "BUCKET_SLUG is not present in the .env", http.StatusInternalServerError)
    return
}

var readKey string
if ok := utils.CheckIfEnvExists("READ_KEY"); ok {
    readKey = "&read_key=" + os.Getenv("READ_KEY")
}

bucketSlug := os.Getenv("BUCKET_SLUG")

url := utils.APIURL + bucketSlug + "/objects?&hide_metafields=true&type=posts&props=slug,title,content,metadata,created_at" + readKey

res, err := http.Get(url)
Then I used `json.Unmarshal` function to process the response and map it to our `ObjectsData` domain model.

var data domain.ObjectsData

if err != nil {
  log.Println(err)
} else {
  body, err := ioutil.ReadAll(res.Body)
  if err != nil {
    log.Println(err)
  } else {
    json.Unmarshal(body, &data)
  }
}
```
Finally, I passed the processed data to the template to render it. Below is the complete code for `src/handlers/index.go` file.
```go
package handlers

import (
  "encoding/json"
  "go-cosmic-blog/src/domain"
  "go-cosmic-blog/src/utils"
  "io/ioutil"
  "log"
  "net/http"
  "os"
  "text/template"
)

// Index handler to serve / route
func Index(w http.ResponseWriter, r *http.Request) {

if ok := utils.CheckIfEnvExists("BUCKET_SLUG"); !ok {
  http.Error(w, "BUCKET_SLUG is not present in the .env", http.StatusInternalServerError)
  return
}

var readKey string
if ok := utils.CheckIfEnvExists("READ_KEY"); ok {
  readKey = "&read_key=" + os.Getenv("READ_KEY")
}

bucketSlug := os.Getenv("BUCKET_SLUG")

url := utils.APIURL + bucketSlug + "/objects?&hide_metafields=true&type=posts&props=slug,title,content,metadata,created_at" + readKey

res, err := http.Get(url)
var data domain.ObjectsData

if err != nil {
  log.Println(err)
} else {
  body, err := ioutil.ReadAll(res.Body)
  if err != nil {
    log.Println(err)
  } else {
    json.Unmarshal(body, &data)
  }
}

t, _ := template.ParseFiles(
  "src/templates/index.html",
  "src/templates/head.html",
  "src/templates/header.html",
  "src/templates/footer.html",
)

t.Execute(w, data)
}
```
Similarly, I created the Single Handler in `src/handlers/single.go`. You can copy code from [here](https://github.com/cosmicjs/go-cosmic-blog/blob/master/src/handlers/single.go).

Now, our routes and controller part is complete. Let's move to render templates.

### Templates
For rendering our templates, I used `text/template` which is easy and quite powerful as well. Furthermore, you can look at this [Go Template Cheat Sheet](https://curtisvermeeren.github.io/2017/09/14/Golang-Templates-Cheatsheet) for your understanding.

For better code structure, I divide the template into `head`, `header` and `footer`. Then `index` template is for index route and `single` template is for single post page. Below is the code of `src/templates/index.html` which is self-explanatory.

```html
{{template "head"}}
{{template "header"}}
  <section class="container mx-auto px-4 py-12 max-w-5xl">
    {{if .Objects}}
      <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 gap-y-12">
        {{range .Objects}}
        <article>
          <a href="/{{.Slug}}">
            <div class="h-64 sm:h-56 lg:h-48 rounded-sm bg-gray-500 bg-cover" style="background-image: url({{.Metadata.Hero.ImgixURL}}?w=400)">
            </div>
          </a>
          <h2 class="text-black text-lg font-semibold mt-2">
            <a href="/{{.Slug}}">{{ .Title }}</a>
          </h2>
          <p class="mt-2 text-gray-600">{{.Metadata.Description}}</p>
          <div class="mt-1"><time class="text-gray-400 font-semibold">{{.CreatedAt.Format "02 Jan 2006"}}</time></div>
        </article>
        {{end}}
      </div>
    {{else}}
      <h2>No post found</h2>
    {{end}}
  </section>
{{template "footer"}}
```

The rest of the templates you can check out here.


## Styling
As you may already notice, I used classes in HTML templates for styling purposes. And these classes are from [Tailwind CSS](https://tailwindcss.com/) which is a modern CSS framework for building custom designs. In order to use Tailwind CSS in this application, I installed npm packages using this command ```bash
npm i -D tailwindcss cssnano postcss-cli
```
and then I created `static/styles/tailwind.css` file with the following content.

Note: you need to have node and npm installed for this
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
For markdown support, I copied [Github Markdown Styles](https://github.com/iandinwoodie/github-markdown-tailwindcss/blob/master/markdown.css) for tailwind into that file as well.

Together with postcss you can build Tailwind CSS and also remove all unused classes in production for the smaller size. You can look at `postcss.config.js`, `tailwind.config.js` and npm scripts in `package.json` in the [code repository](https://github.com/cosmicjs/go-cosmic-blog).

Finally, for development purposes I installed `reflex` which is a tool to detect code changes and run our `go run app.go` command (Note: `reflex` only works on macOS and Linux). You can install that using this command:

```bash
go get github.com/cespare/reflex
```
Voila, our blog application is ready. You can run npm start and go to [http://localhost:8000](http://localhost:8000) to access the application. It should look something like the screenshot below:
![Cosmic Go Blog](https://imgix.cosmicjs.com/549e12b0-f84c-11ea-8023-b50824df693d-go-blog-cosmic.png)


## Deployment
At this point, the blog application is fully functional locally. But I wanted to deploy this to Heroku as it is very easy for deployments and it supports Go applications. As this repository is on GitHub, I thought to give [GitHub Actions](https://github.com/features/actions) a try.

I looked around and found a very easy GitHub action for Heroku deployments [here](https://github.com/marketplace/actions/deploy-to-heroku).

To set up it properly, we need to make sure our Go application has a `go.mod` file. Also, I need to add secrets in our GitHub repository settings i.e `BUCKET_SLUG`, `HEROKU_API_KEY`, `HEROKU_APP_NAME`, `HEROKU_EMAIL`, `READ_KEY`.

After that, I just needed to use `actions/checkout@v2` to check out our repository and then trigger Heroku deploy. Below is the full code for our Github Action file `.github/workflows/production.yml`.
```yaml
name: Production Deployment

on:
  push:
    branches: master

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
    - name: Check out code
      uses: actions/checkout@v2
    - name: Deploy to Heroku
      uses: akhileshns/heroku-deploy@v3.4.6 # This is the action
      with:
        heroku_api_key: ${{secrets.HEROKU_API_KEY}}
        heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
        heroku_email: ${{secrets.HEROKU_EMAIL}}
        branch: "master"
      env:
        HD_BUCKET_SLUG: ${{secrets.BUCKET_SLUG}}
        HD_READ_KEY: ${{secrets.READ_KEY}}
```
This will trigger the deployment on very commit in the `master` branch.


## Conclusion
After this whole journey, I can confidently say that Go is a super simple and robust language to build your backend application. In addition, these are my takeaways:

Tailwind is the best CSS framework for building custom designs. It is very customizable.
It is very simple to add CI/CD to our application using GitHub Actions and it has very generous developer-friendly free tier.
Application deployments are very simple and easy with Heroku.

Hope you liked this article. If you have any comments or questions about building websites and apps with Cosmic, [reach out to us on Twitter](https://twitter.com/cosmicjs) and [join the conversation on Slack](https://www.cosmicjs.com/community). You can also follow another step-by-step [Go Guide in the Cosmic Docs](https://docs.cosmicjs.com/guides/#go).