Using Directus as a CMS for my Blog
A flexible SQL DB interface that really shines ✨
Blogs. Such a simple concept. So many different ways to create them. Most people reach for Wordpress, Wix, or Squarespace. Software developers often choose a more DIY approach. A popular method of creating a blog is using a static site generator.
A Little Backstory...
The very first static site generator I used was one I accidently made myself! Well kind of. I was very new to web development, and I was messing around with React via create-react-app. I skipped right from manually coding html files in a static site to messing around with a React SPA, and I was not even familiar with the concept of a static site generator at the time. I thought "Wow, wouldn't it be cool to write markdown and have it render in this SPA?" So I messed around with Webpack's file loading capabilities until I had html strings loaded into the SPA and rendered on link clicks. 🤣 Looking back, I laugh at how ridiculously complex this setup is for a site which could have just been a simple, flat, static site. Not too long after that, I learned about proper static site generators.
Once I started reading about static site generators, I quickly came across Hugo. I really liked it! It was so fast, relatively simple, and capable of creating large static sites easily. I made several sites with it, and it was great.
Limitations of Static Site Generators
One of the drawbacks of using Hugo or any other static site generator operating on flat files in a repository is the lack of a built in user interface for writing the content. Of course, the user interface is the text editor of your choice. This is usually not a problem for a software developer. We are used to editing text files with peculiar syntax, after all. Of course, we are the minority. If you want to allow non-technical users to edit a website, sending them a link to a Git repo and telling them to edit the Markdown in the
content directory is not going to go smoothly. And if you want to edit your content on a mobile device, good luck. You'll need a way to make commits. If you want to allow non-technical users to edit the content on their mobile devices, you are really in for a challenge!
As I started making static websites for other people, I was running into these issues more and more. Editing content on my mobile device is particularly important to me because I'm a digital nomad at the moment, living out of my 2010 Ford Fusion. I'm currently writing this blog post in Nederland, CO way up in the Rockies! Powering devices in the middle of nowhere is difficult, and my iPhone SE 2020 consumes far less energy than my 2013 Macbook Air.
I eventually realized I wanted a slick, responsive web app with multi-user functionality that I could use to edit Markdown. I tried forestry.io (limited free tier) and Netlify CMS (free). Those were alright. Forestry.io integrates with popular static site generators and is much more slick and capable but costs quite a bit of money for it's full functionality. For reference, here is a Hugo project I intergrated with forestry.io.
Switching Static Site Generators
In early 2020 I started learning about Clojure. I loved using it! I decided to convert my personal blog from a Hugo project to a Clojure project. Instead of using a typical static site generator, I decided to use Stasis, a Clojure library that provides a set of functions to easily construct your own custom static site generator. Stasis allows me to choose how to store the content. Like typical static site generators, it can be stored as Markdown files inside the repository. However, you can also store content in a database. This got me thinking: What open-source CMS web apps are available to use as a backend interface over a database? I searched through GitHub search results until I found a true open-source gem: Directus.
What is Directus?
Directus is a GPL-V3 licensed Node.js (as of v9) web app that acts as slick intermediary between site admins, content editors, and a SQL database where all the data is stored. It's quite simple in it's approach. Through the interface you can create new tables, new columns, change how columns are displayed in the UI, set up roles with varying permissions, and even adjust the theming of the web app. The app also generates REST and GraphQL endpoints for retrieving data from the SQL database. The frontend is written in Vue components. The interface Directus provides is remarkably capable and simple. For a much more thorough explanation check out the introduction section of the docs.
One of the best features of Directus is the polished UI components. For long text content there is a built in WYSIWYG component and a Markdown editor component, both with toggable previews and visually pleasing syntax highlighting. For slugs there is a component that automatically converts spaces into hyphens and doesn't allow non-URL characters. For list-like data like tags, there are options to create a prepopulated multi-select option list so you can just click on the tags you want or add your own in an input field, saved to the database as comma separated values. From the interface, you can control if a field can be empty, if a field has a character limit, if a field must be unique, etc. Typical SQL operations that would require a big SQL command suddenly become trivially easy. Helpful hints are given when issues arise such as when a column is given a unique constraint when there are already non-unique values.
The lower level configuration for the Directus app is stored in a
.env file in the project root. Here you provide the database connection information, the session secret, the optional S3 storage configuration, etc. To get a fuller picture of what Directus does, check out all the environment variable options.
The configuration for the UI and roles are stored inside of the SQL database as tables which Directus manages internally. This means that backing up the database also saves the current roles and chosen UI components.
Another handy feature built into Directus is webhooks. When Directus makes updates to the database, it checks if any webhooks have been configured to trigger for that particular table. If it finds one, it sends a request. Webhook configuration is also saved to the SQL database as a table.
How I Use Directus as CMS
The way I use Directus is this: Directus runs as a public facing web service at cms.stel.codes proxied by nginx running on a cloud server with NixOS installed. For saving state, the Directus app uses a PostgreSQL database running on the same machine which is not exposed to the outside world. My Clojure-built static site generator starts a web server that listens on a special port for a HTTP request from the Directus app. When it recieves a request, it rebuilds the blog twice. The first build includes the content marked as
draft and is a preview hidden behind a basic auth login via nginx at preview.stel.codes. The second is the actual production website and this gets served publicly via nginx at stel.codes. I'm loving this setup so far! I can preview the changes and simply publish articles by marking them as
To back up the PostgreSQL database, I use the
pg_restore utilities via a set of Babashka tasks I created that provide backup directory creation, backup timestamps, confirmation dialouges, helpful messages, and easy configuration. My
bb.edn file with these tasks is open-source and available on Github here.
So that's a brief overview of how I started using Directus and how I update and rebuild this website. Thanks for reading about my blog while visiting my blog! So meta. :)