Build Content Management System with Micro

By Oleksandr (Sasha) Antoshchenko

5/14/2024

Case Study

TL;DR

Discover how we leveraged Micro to build and deploy a fully functional content management system in just two hours. This case study highlights the efficiency and practicality of Micro, showcasing its ability to quickly turn ideas into production-ready APIs.

Motivation

While building this website, I wanted to be able to post and edit articles without a need to edit HTML and deploy it every time. The way this is normally done is via Content Management System (CMS). I have never used one myself so for me to learn how to use one would take time. Instead, I decided to build one myself. This does two things for Svtoo:

  1. Gives us a good real-world project to implement with Micro.
  2. Allows Svtoo to own the data and the functionality, so that we can grow it as company grows, and adopt it to the ever-changing needs of the company.

Architecture

We need to build a typical full-stack application, with a UI that allows the users of CMS to post and edit articles, the backend that encapsulates the data validation and storage as well as distributes the content itself, and the database that stores the data.

Here is a very high-level overview of the architecture:

flowchart TD subgraph CMS ui[UI] be[Backend] db[(Database)] end website("svtoo.com") ui -->|Post and Edit Articles| be be -->|Store| db website -->|Pull Articles| be

Technology

For the Database, we will use DynamoDB. It is a NoSQL database that is very easy to use, no-hustle to set up, and only costs money when you use it.

We decided to build the UI using Retool. It is a very powerful tool that allows you to build UIs with a drag-and-drop interface. It is very easy to use and allows you to build very complex UIs with very little effort.

And you might have guessed it, the Backend will be built using Micro. It is a perfect fit for this project as it allows us to build a very scalable and maintainable backend with very little effort. This project perfectly demonstrates the practicality and efficiency of Micro in building real-world systems.

Implementation

Here I will go through the implementation of the backend part of the CMS. I will omit details here but will dive deep into it in the video below.

The database is straight forward. DynamoDB has no schema, so we just put the data in there. You will see how the data looks like when we get to the backend part. The only thing worth noting here is that to call the DynamoDB API we need to provide the credentials. For that I have simply created a new user in AWS IAM and provided the credentials in the code. This is not a good practice and will be changed in the future with the introduction of secrets management.

I will be skipping the UI part as well, since it is not the focus of this article.

To manage content we need at minimum two operations Create/Update and Get All articles. I decided to combine Create and Update because there is no reason not to at this stage. I wanted to keep it as simple as possible. For the same reason there is no query options, no sorting, filtering, etc. just Get All articles. When we have written so many of them that this becomes a problem we will come back to it.

Micro is called that for a simple reason, it allows you to create microservices. This means micro supports only one type of request in its schema, which means to implement our endpoints we need to build two Micro-Applications . One for Create/Update and one for Get All.

We will look in depth at the Create/Update Micro-Application only as the Get All is very similar.

Schema

As described in Downstream calls Release, Micro distinguish between the Upstream schema and the Downstream schema. The Upstream schema is the schema of the request that the Micro-Application itself serve, and the Downstream schema is the schema of the request that the Micro-Application makes to the downstream service.

Upstream Schema

Here is the Request schema for the Create/Update:

{
  "title": "",
  "teaser": "",
  "featuredImage": "",
  "publicationDate": 1,
  "published": true,
  "content": "",
  "type": "",
  "path": "",
  "author": ""
}

The website already "understands" how to render this data, in particular the content and teaser fields are in markdown. The website will render the markdown as HTML. published controls whether the article is visible on the website or not. path is the URL of the article. type is the type of the article, for example, case-study, blog.

There are two responses possible:

  • Success
{
  "publicationDate": 1,
  "lastUpdated":1
}
  • Error
{
  "error": ""
}

In case of Success the response contains both publicationDate and lastUpdated fields, since former is optional parameter and latter is always set to the current time.

Downstream Schema

The Downstream schema is almost identical to the Upstream schema since we just want to save the data in our database. Since Micro will take care of the validation, all we have to do is provide a tiny piece of code to save the data in the database. Using the recently released ability to import Node.js libraries, we add a dependency on @aws-sdk/client-dynamodb and @aws-sdk/lib-dynamodb, and use it in our code:

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';

// Create a DynamoDB client
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({
    region: "***",
    credentials: {
        accessKeyId: "***",
        secretAccessKey: "***"
    }
}));

// Micro requires you to implement the handler function with the following signature
async function handler(event) {
    // Micro supplies the data in the event.request.body. The body will be in the format you defined in the Upstream Request schema
    const itemData = event.request.body;
    const timeNow = new Date().getTime();
    itemData.lastUpdated = timeNow;
    if (!itemData.publicationDate) itemData.publicationDate = timeNow;
    try {
        const data = await docClient.send(new PutCommand({
            TableName: 'website-content',
            Item: itemData
        }));
        // You need to return the response in the format you defined in the Downstream Response schema
        // This is the success response
        return {
            publicationDate: itemData.publicationDate,
            lastUpdated: itemData.lastUpdated
        }
    } catch (err) {
        // This is the error response
        return { error: err }
    }
}

A few things to note in the code above:

  1. The credentials are currently hardcoded. This limitation will be removed in the future with the introduction of secrets management.
  2. The publicationDate and lastUpdated fields are set in the code and not by Micro because, at this point, Micro does not allow flexible "assertions" in the Use-Cases. This means we cannot specify that a field needs to be greater than a certain value; we can only compare equality. This feature will be added in the future.
  3. The code does the minimum necessary to save the data in the database and returns the publicationDate and lastUpdated fields. We want Micro to handle most of the logic for us, so we keep the code as simple as possible.

I want to emphasize the last point. The code above is the only code we need to write to save the data in the database. Micro will take care of the validation, error handling, and response formatting. This is the power of Micro.

Use-Cases

The way you dictate what you want the Micro-Application to do, is by defining what we call Use-Cases. If you are a software engineer you can think of Use-Cases as a test-case, otherwise it is just an example of input/output of the Micro-Application.

Simply put, try imagining you are testing existing application, to make sure it does exactly what you want. This is exactly what you do when you define Use-Cases, the only difference is that your application does not exist yet. The benefit, you have acceptance tests ready to go, and you always know if your application does what it supposed to.

As our application is fairly simple, we mostly focus on three types of usecases:

  1. Validation Error, when we want Micro to validate the input data.
  2. Downstream Error, when we want Micro to propagate from the database call to the response.
  3. And of course Success, when we want Micro to return the correct response.

Video

You can see the full walk through this application in the video below:

Conclusion

Building a content management system with Micro was a straightforward and efficient process. In just two hours, we developed and deployed two production-ready APIs. This experience has demonstrated the practical power and versatility of Micro, the product we are building at Svtoo. By handling the heavy lifting of validation, error handling, and response formatting, Micro allows developers to focus on the core logic of their applications.

Micro's ability to rapidly turn ideas into functional systems is a game-changer. It has proven to be an invaluable tool for us at Svtoo, providing a scalable and maintainable backend solution. We even used Micro to help build itself, showcasing its robustness and flexibility.

As we continue to grow and evolve, Micro will be at the heart of our development strategy, enabling us to adapt quickly to new challenges and opportunities. If you're looking to streamline your development process and build robust systems with minimal effort, Micro is the tool for you. Its simplicity and efficiency make it a must-have for any development team. We are excited to see how Micro will continue to enhance our projects and look forward to the new features and improvements on the horizon.

Stay connected!

Please consider singing up for the waitlist , to be one of the first ones to try Micro.

Interested in working with us, investing in Svtoo, or any other question, please do not hesitate to Contact Us .