Using Nimble to build a CMS

Scott Wyatt

What is a CMS?

A CMS is short for a Content Management System. Using a CMS is one of the most effective ways to mitigate the technical burden marketing teams incur on development and IT. A CMS allows marketing teams to focus on content creation and management, while developers handle the technical aspects of the website. There are several popular CMSs, such as Wordpress, Drupal, and Joomla. However, these CMSs are either not robust enough or are often too complicated to meet the needs of many modern marketing teams. Not only are these CMSs difficult to scale, CMSs in general can actually be a security risk to your organization. This is because they almost always require one or more databases, servers, and authentication strategies. The databases are often exposed to the internet through the website, which can lead to a security breach of sensitive material or vandalism. Additionally, CMSs are often not very secure or scalable by default. This means that they are often vulnerable to attacks and outages unless configured perfectly. To mitigate this risk, many organizations will hire security experts and Site Reliability Engineers to configure their CMS. This is an expensive and time-consuming process but well worth the effort.

Headless CMSs

An alternative is to use third party CMSs such as a headless CMS. In a headless CMS like Storyblok, Ghost, or Prismic, the content is stored in a managed database, but the website is not. Instead, the website can be built using a static site generator. This means that the website is built once and then served statically. This method can make the website less vulnerable to attacks and also makes the website very scalable and fast because it is served statically. This is a huge advantage over a traditional CMS. However, this also means that the website is not dynamic. Static websites cannot be updated without re-bundling the website. This is a huge disadvantage over a traditional CMS where content publishing is much faster.

There is also a hybrid approach with Headless CMSs. This approach is to use the CMS as a JSON feed and dynamically load content through a JavaScript or PHP framework. This is a good approach, but it is not as fast as a static site generator. Additionally, it is not as secure as a static site generator. This is because the CMS is still exposed to the internet. This means that the CMS is still vulnerable to attacks. This is a huge disadvantage over a static site generator.

Building a CMS with Nimble

Nimble is an N Lang Information Model Framework for creating blazing fast, cloud-ready, visual applications. Nimble inherits its flexibility from N Lang, a stunningly easy language of shortcuts. We could use Nimble to build either a Static or Dynamic CMS. However, why stop there when we can make a hybrid of a hybrid that gets all the speed and security benefits of static, and the lightning fast publishing of dynamic?

First, Nimble can be used as a Headless and "Tailless" CMS. This means that the content API can be used to serve content as an API, but without a database. Additionally, Nimble can also structure the entire content for a dynamic or static website. This works because the Parsing, Interpreting, and Runtime are seperated in N Lang which means they are seperated in Nimble as well.

Nimble as a Headless CMS

Nimble's absolutely ridiculous re-usability allows it to easily create a static content API in JSON. This means, we could easily replace our Headless CMS API with one that is static but quickly update-able. This also means that the content can be loaded dynamically which will give a huge performance boost over a database driven CMS API since our content feed can be cached on Content Delivery Networks (CDNs) or Edge functions. However, Nimble can be used for more than just text structure, it can also create "blocks" of N Lang that can be rendered into nearly any visualization.

Nimble as a Tailless CMS

Since Nimble can be used to create both content and blocks to shape the content, we can skip the Headless concept and go straight to tailless as well, which means we can create an extremely robust CMS without having to rely on CDNs, Databases, or an API. We can simply structure all of our content and shape in Nimble N Lang blocks. From here, we have options between rendering the website statically or dynamically. Since Nimble can be both headless and tailless, we can find the perfect blend of speed and security for our needs all while having a top-notch developer experience that leaves the marketing team impressed as well. In fact, the website you are reading this on is a Tailless CMS built with Nimble!

Getting started

To get started, first you'll need to log in to your Squillo Square. Squillo Square will help us build, test, and deploy our CMS. From your Squillo Square app, either create a new Snapp or open an existing one.

Creating a new document

Now, create a new file called index.n.md. This will allow us to write N Lang code alongside our documentation which will make our lives much easier in the future. In our index.n.md file, let's add our document front-matter.

@name = "my_cms"  
@version = "1.0.0"  
@description = "My CMS root document"
@extends = "[email protected]"
@nimble = "1.0.0"

We can use this document as the starting point for all the other N Lang documents we are going to write.

Next, it's important to mention some of Nimble's pre-made block types. Nimble comes with a few pre-made block types that can be used to create a CMS. The most commonly used block types are layouts, slots, pages, and components. There are several more that are very powerful, but these are all we need to get started. The rules of these are fairly straight forward. layouts contain components and slots, slots contain pages, and pages contain components. These simple rules allow us to create a very powerful CMS and pretty much any type of visual application. You can learn all about the different Nimble blocks in the Nimble Documentation. Let's start by creating a layouts block. In our index.n.md file, let's add the following code:

layouts "HOME" {
  description: "The Primary Layout"
}

Great! We've created a named layouts block. Now, let's add a named slots block to our file, so we can add it to our layout. In our index.n.md file:

slots "MAIN_SLOT" {}

Wow, that was easy. Now we can add our slot to our layout. In our index.n.md file:

layouts "HOME" {
  description: "The Primary Layout"
  components {
    MAIN_SLOT {}
  }
}

A layout just wouldn't be complete without a header and a footer. Let's add those so we can add them to our layout. In our index.n.md file:

components "MAIN_HEADER" {}

components "MAIN_FOOTER" {}

Now let's add them to our HOME layout:

layouts "HOME" {
  description: "The Primary Layout"
  components {
    MAIN_HEADER {}
    MAIN_SLOT {}
    MAIN_FOOTER {}
  }
}

Great! Now our layout has a header, a slot, and footer. Next, let's add some components to our MAIN_HEADER to give it some branding identity. We'll start with just our company name. In our index.n.md file:

components "COMPANY_NAME" {
  properties {
    content en {
      text: "Squillo"
    }
  }
}

Lets breakdown the above since there are some things we haven't seen yet. The components block definition allows for a property called properties. For our CMS, we want to support a way to display text content and also have something like i18n support. We are setting a property called content that has a property called en which has a property called text. This is a very powerful way to structure our content. The en will act as a language code and the text will be the actual text content. This will allow us to easily support multiple languages if we want to.

Now, let's add our COMPANY_NAME component to our MAIN_HEADER component. In our index.n.md file:

components "MAIN_HEADER" {
  components {
    COMPANY_NAME {}
  }
}

Excellent! So now where ever we use our MAIN_HEADER component, it will automatically have the COMPANY_NAME component included. This is starting to form a nice tree structure.

Let's show off some really nice N Lang features to create some links to add to a MAIN_NAV component to our MAIN_HEADER component. In our index.n.md file:

First, let's create an anchor component that will be used as a generic to create links.

components "anchor" {
  type: "anchor"
  properties {
    href: null
    content en {
      text: null
    }
  }
}

Note: we're using the namespace "anchor" lowercase here to denote that this is a generic block. This is a convention that we will use throughout this tutorial but isn't required.

We are going to go a step further and enforce that all anchor components have a href and content property. We can do this by updating the local block definition to use the required property. In our index.n.md file:

components "anchor" {
  type: "anchor"
  properties {
    // We'll add a default value to warn us if we forget to add an href but still pass validation.
    href: "#"
    content en {
      // We'll add a default value to warn us if we forget to add text but still pass validation.
      text: "`content.en` defined without text"
    }
  }
  @block {
    // enforce that all anchor components have a href and content property
    properties properties {
      required: ["href", "content"]
      href {
        type: "string"
      }
      content {
        type: "object"
        properties {
          en {
            type: "object"
            required: ["text"]
            properties {
              text {
                type: "string"
              }
            }
          }
        }
      }
    }
  }
}

This is great! Now every time we have an anchor, we can extend it off this block and enforce these rules. Next, we want to be able to create a link that will be used as a home link. Let's create a HOME_LINK and BLOG_LINK component that will extend the anchor component. In our index.n.md file:

components "HOME_LINK($.components.anchor)" {
  properties {
    href: "/" 
    content en {
      text: "Home"
    }
  }
}

components "BLOG_LINK($.components.anchor)" {
  properties {
    href: "/blog" 
    content en {
      text: "Blog"
    }
  }
}

components "MAIN_NAV" {
  components {
    HOME_LINK {}
    BLOG_LINK {}
  }
}

Now we can add our MAIN_NAV component to our MAIN_HEADER component. In our index.n.md file:

components "MAIN_HEADER" {
  components {
    COMPANY_NAME {}
    MAIN_NAV {}
  }
}

Great! Now we have a header with a company name and a navigation. It's a good time to create a home and blog page. Just like with anchor, we are going to create a page generic to enforce some rules for our CMS pages. In our index.n.md file:

pages "page" {
  properties {
    uri: ""
    title en {
      text: "`title.en` defined without text"
    }
  }
  @block {
    // enforce that all pages have a uri and title property
    properties properties {
      required: ["uri", "title"]
      uri {
        type: "string"
      }
      title {
        type: "object"
        properties {
          en {
            type: "object"
            required: ["text"]
            properties {
              text {
                type: "string"
              }
            }
          }
        }
      }
    }
  }
}

Now we can create our home and blog pages. It's a good idea to create new directories and files for pages, but for this tutorial, we'll put them in our index.n.md file:

pages "HOME_PAGE($.pages.page)" {
  properties {
    uri: "/"
    title en {
      text: "Home"
    }
  }
}

pages "BLOG_PAGE($.pages.page)" {
  properties {
    uri: "/"
    title en {
      text: "Home"
    }
  }
}

Remember how we made those links in our MAIN_NAV component? Let's use N Lang's path/1 function for those to get the uri and title properties as the href and content to simplify things. In our index.n.md file:

components "HOME_LINK($.components.anchor)" {
  properties {
    href: path($.pages.HOME_PAGE.properties.uri) 
    content: path($.pages.HOME_PAGE.properties.title)
  }
}

components "BLOG_LINK($.components.anchor)" {
  properties {
    href: path($.pages.HOME_PAGE.properties.uri)
    content: path($.pages.HOME_PAGE.properties.title)
  }
}

Whoa! How cool is that? Now, we can drive the links and text directly from the page data and never worry about them getting broken!

Finally, let's add some content to our home page. In our index.n.md file:

components "WELCOME_BANNER" {
  properties {
    content en {
      text: "Welcome to our site!"
    }
  }
}

pages "HOME_PAGE($.components.page)" {
  properties {
    uri: "/"
    title en {
      text: "Home"
    }
  }
  components {
    WELCOME_BANNER {}
  }
}

Now we have a home page with a welcome banner. Let's add a welcome banner to our blog page as well. We'll reuse the WELCOME_BANNER component but replace the text inline. In our index.n.md file:

components "WELCOME_BANNER" {
  properties {
    content en {
      text: "Welcome to our site!"
    }
  }
}

pages "BLOG_PAGE($.pages.page)" {
  properties {
    uri: "/blog"
    title en {
      text: "Blog"
    }
  }
  components {
    WELCOME_BANNER {
      properties {
        content en {
          text: "Hello World!"
        }
      }
    }
  }
}

WOW! Did you see that?! We created a component once, and then could spot change it for a particular usage somewhere else! This also means we have a nice builtin way to reuse components across pages and track where those components are being used — fast!

Let's up our game though. Let's set the WELCOME_BANNER to automatically display the title of the page through our generic page component. In our index.n.md file:

pages "page" {
  properties {
    uri: ""
    title en {
      text: "`title.en` defined without text"
    }
  }
  components {
    WELCOME_BANNER {
      properties {
        // We'll use the relative path/1 syntax to get the title from the page where this block is included.
        content: path(^^^@.properties.title.en.text)
      }
    }
  }
  @block {
    // enforce that all pages that extend `page` have a `uri` and `title` property
    properties properties {
      required: ["uri", "title"]
      uri {
        type: "string"
      }
      title {
        type: "object"
        properties {
          en {
            type: "object"
            required: ["text"]
            properties {
              text {
                type: "string"
              }
            }
          }
        }
      }
    }
  }
}

Which means, we can remove the welcome banner from our home and blog pages, and it will be included automatically. In our index.n.md file:

pages "HOME_PAGE($.pages.page)" {
  properties {
    uri: "/"
    title en {
      text: "Home"
    }
  }
}

pages "BLOG_PAGE($.pages.page)" {
  properties {
    uri: "/blog"
    title en {
      text: "Blog"
    }
  }
}

Pretty nifty right? We've just created a reusable page type that we can use over and over again, and a welcome banner will also be included with the correct content!

Note: If you've used CMSs before, you know how much time this saves you. If your jaw is starting to hurt from all the dropping, we haven't even scratched the surface of what we can do yet.

To wrap up, we now have a Header with our company name and navigation, we have a footer, as well as two pages for Home and Blog all in just a few lines of code. We could really get going creating more components for our library where our Marketing wizards could work their magic.

Next Steps

The next steps are pretty straight-forward. We'll parse our documents into a snapp through Squillo Square and select the Nimble Interpreters we want to display it. Where this gets fun is we can select multiple interpreters for the same snapp and put this on the web or even a mobile app and manage everything from one Nimble snapp! We'll probably want to add some styling as well, but those may be specific to the Nimble interpreter so we'll wait until we select one or more in the next tutorial. We could also use the other N Lang frameworks to create automations and deployments for our new CMS, but we'll leave that for another tutorial series.

Let’s Snapp™ the world together

Squillo is network-native technology that’s as capable in demanding product facing APIs as it is in internal development platforms.

Login

Login is currently restricted.

Search

Search is currently restricted.