Advertisement
  1. Code
  2. CMS
Code

Building a CMS: goPress

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Building a CMS.
Building a CMS: Structure and Styling

Go is a network-centric programming language developed by Google that makes writing network related programs easy. With the many great libraries from which to choose, getting a web application running is a snap.

In this tutorial, I am going to create a Content Management System (CMS) using Go and some helper libraries. This CMS will use the site data structure as laid out in the first tutorial, Building a CMS: Structure and Styling.

Development Setup With Go

The easiest way to install the go programming language on a Mac is with Homebrew. If you haven’t installed Homebrew yet, the tutorial Homebrew Demystified: OS X’s Ultimate Package Manager will show you how. For other platforms, just follow the instructions on the Go download page.

In a terminal, type:

In your home directory, create the directory go. The Go language will store all downloaded libraries there. Add to your .bashrc file and/or .zshrc file this line:

If you’re using fish, add this to your config.fish file:

Next, you need to install the libraries. The goWeb library supplies the web server framework, the amber library gives the Jade equivalent HTML preprocessor, and BlackFriday translates Markdown to proper HTML. Also, I use the Handlebars library for templating. To install these libraries, you need to type the following in the project directory:

Now that Go and the needed libraries are on your system, the next step is to start coding. The goPress library has five files, while the main program is one file to call the library functions.

goPress.go Library File

I wanted the server to be as fast as possible. To achieve this, anything that is reusable is in memory. Therefore, a global variable keeps all of the assets and home page. You could design the server with all assets in memory, but this would cause memory bloating on large sites. Since the home page should be the most frequently loaded page, it is also kept in memory.

Using the file structure set up in the last tutorial, create a new directory in the src directory called goPress. This will be where all goPress library files are placed. The first file is goPress.go. Create the file and start to put in the following code.

The package statement at the top tells the compiler that this file is a part of a package library and gives the name of the library. Every file in this directory has to have this at the top to be a part of the file.

Next, you import all libraries referenced in this file. If you list a library and do not use it, the compiler will complain. This helps keep your code clean and tidy.

After loading the libraries, I define the different data structures and global variables that the CMS will use. These globals are library global. Outside the library, variables that begin with a capital letter can be referenced if the library name scopes it.

Next, add this function to the same file:

The GetGlobals function loads all the globally stored information for the site. A hash map based on file name (without extension) stores the data from the server file, the layouts directory, and the styles directory. Then, everything in the site/parts directory is put into the same structure. This way, if the site just wants the defaults given in the theme, the user doesn’t have to put a file for it in the site/parts directory.

In the same file, add these functions:

These are the helper functions SaveServerParameters() and LoadServerParameters(). These functions save and load the different server settings to the server.json file.

The next functions are for creating routes and our default routes. Add these functions to the same file:

The DefaultRoutes() function creates the default routes to use in our CMS. The functions for these routes are in the other library files. The SetGetRoute() creates each route. This is simply a wrapper over the goWeb library function that takes a regular expression to define the format of the route and a function to execute when that expression is true. If you’ve ever used the Sinatra framework for Ruby or the Express framework for Node.js, then you will be familiar with this setup.

The creation order for the routes is important. If the first route contains a regular expression to match everything, then the rest of the routes aren’t accessible. The first route would catch them all. Therefore, I defined the most specific routes first, and the more general routes last.

The StartServer() function starts the web server. It calls the goWeb function Run() that takes the address for the server.

Throughout the code, I make good use of the log.PrintLn() function. This prints to the console the message given with a date and time stamp. This is great for debugging, but is also used for traffic analysis.

PagesPosts.go Library File

Next, create the PagesPosts.go file in the same directory. This file will contain all the code for working with pages and post types. A page is simply a web page. A post is anything created over time: news posts, blog posts, tutorials, etc. In this file, add the following code:

As in the goPress.go file, it starts with the package declaration and the list of libraries to import. This file will make use of every library we downloaded for go.

The Mainpage() function shows the front page of the site. It is simply a wrapper for the RenderPageContents() function specifying the main index page to be rendered. RenderPageContents() does all the real work.

The SiteMap() function gives the sitemap to the requester. It pulls the information from the sitemap.xml at the top of the site directory.

The PostPages() function displays the proper post requested. Once again, this just sets up the call to the RenderPageContents() function, which does all the main work.

The PostIndex() function pulls together the information for a post index and gives it to the RenderPageContents() function.

The topPages() function sets up the RenderPageContents() function for a standard page. All pages are in the pages/ directory.

The GetPageContents() function loads the contents of all of the pages/posts. It first loads the 404 not found page contents from the global data structure. The function then looks for a Markdown file first, then a HTML file, and then an Amber file. Next, the routine converts all Markdown and Amber content to HTML. The Amber file might have associated data in a JSON file. That data file is also loaded for the processing of the Amber file.

The Markdown processing in Blackfriday has a consequence for the Handlebars processor. The Blackfriday markdown to HTML processor changes all double quotes to its HTML escaped equivalent (“ and ”). Since that is not 100% necessary for rendering, I reversed the change afterwards. This keeps all Handlebars macros that use double quotes functional.

If you want more file format types, just add them here. This routine loads every content type.

RenderPageContents() is the main function used to create a web page. After it sets the standard header for the reply, this routine creates a data structure and fills it with the default contents, page contents, and an associated JSON file for the page. The Handlebars templater uses the data structure to render the entire page.

Next, the routine defines all of the Handlebars helper functions. Currently, there are two: save helper and date helper. If you would like more helper functions, this is where you would add them to your project.

The save helper takes two parameters: a name separated from contents by a |. Since the Handlebars helper parameters cannot contain spaces, the parameters use a - instead of a space. This allows you to create per page template variables inside the context of the page. For example, the {{save site|Custom-Computer-Tools}} macro will place Custom Computer Tools at the point of definition and anywhere else on the page that has {{site}}.

The date helper takes a format string and creates the proper date according to that format string. For example, the {{date January-2,-2006}} macro produces October 13, 2015 on that day.

The Handlebars templater processes the page twice: before shortcodes are rendered in case any shortcodes are in the template expansion, and after running the shortcodes in case a shortcode adds any Handlebars template actions. By the end, the function returns the full HTML contents for the requested page.

The SetStandardHeader() function sets any custom header items into the reply. This is where you set the server information and any caching controls.

Images.go Library File

The next file to work on is the Images.go file and all the functions needed to send an image to the browser. Since this will be a full web server, it has to deal with the binary data of sending an image. Create the file Images.go and put this code in it:

This library file starts just like the others: a package declaration and the library declarations. The ImagesLoad() function and the LoadThemeImage() function set up a call to the LoadImage() function to do the actual work. These functions allow for the loading of images from the site directory or from the current theme directory.

The LoadImage() function checks the image type. If it is a svg file, then it loads as plain text. Assuming all other file types are binary files, the routine loads them more carefully. It will upload binary files in 1K blocks.

StyleSheetScripts.go Library File

The next file is for loading CSS and JavaScript. Since our build script compiles all the CSS and JavaScript to a single file each, these functions are really simple. Create the file StyleSheetScripts.go and add these lines:

This file has three functions. The GetStylesheets() function loads the compiled CSS file. The GetScripts() function loads the compiled JavaScript file. With the cache flag set, both of these functions will cache the contents. I turn off the Cache flag while testing. The LoadFile() function is a simple file loading function for getting the file contents.

Shortcodes.go Library File

While I wanted a fast server, I also want a lot of flexibility. To achieve the flexibility, there are two different types of macro expansions: direct Handlebar expansion, and shortcode expansion.

The difference is that the Handlebars expansion is a simple, low-logic expansion, while the shortcode expansion is anything that you can program into the system: downloading information from an external site, processing information with an external program, or just about anything.

Create the Shortcodes.go file and place this into it:

This file starts like all the rest with a declaration of the package and libraries used. But it quickly is different with the defining of a special ShortcodeFunction variable type, a library variable, and an init() function. Library variables are only seen by a function of the library. This library variable, shortcodeStack, is a mapping of strings to a function.

The library init() functions allows you to run code before any other calls to the library. Here, I initialize the shortcodeStack data structure for holding the list of shortcodes.

The AddShortCode() function allows you to load a function for processing a shortcode into the library variable for all shortcodes.

The ProcessShortCodes() function takes a string that is the web page contents and searches for all shortcodes in it. Therefore, if you have a shortcode called box, you would insert it in your webpage with this format:

Everything after the space in the shortcode opener is the arguments for the shortcode to process. The formatting of the arguments is up to the shortcode function to process.

All short codes have to have a closing shortcode. What’s inside the opening and closing shortcode is the process for shortcodes as well before sending to the shortcode processing function. I use the -[]- to define a shortcode so that inline JavaScript indexing doesn’t get confused as a shortcode.

The last section of code defines seven simple shortcodes and adds them to the shortcode array using the LoadDefaultShortcodes() function. If you want a different functionality, you just have to change this code and it will update it everywhere in your web site.

goPressServer.go Main Program File

The last file to create is the main program file. In the top of the development directory, create the file goPressServer.go and place this information:

The main() function is the routine called when the program runs. It will first set up the shortcodes, load in global variables, set the default routes, and then start the server.

Compiling and Running

To compile the whole program, move to the top directory where the goPressServer.go file is and type:

If all the files are in place, it should compile to goPressServer on the Mac and Linux systems, and goPressServer.exe on Windows.

Running goPressServer in the Terminal

When you execute the program in a terminal, you will see its log statements with the date and time as above.

The Front Page From the Server

If you open your browser to the server’s address, you will get the front page. You will see the example shortcode and the two different Handlebars helper functions used. You now have your own web server!

As you can tell, I changed the front page and added three more pages to the original site design given in the tutorial Building a CMS: Structure. I also added the JavaScript library Syntax Highlighter in the site/js/ directory for displaying the code on the web page using the shortcode.

All of these changes are to show off the Handlebars and shortcode processing. But, due to Syntax Highlighter not working well with compression, I removed the JavaScript compression from the Gulp file. All of the changes are in this tutorial’s download file.

Conclusion

Now that you know how to build a simple yet powerful webserver using the go language, it’s time for you to experiment. Create new pages, posts, embeddable parts, and shortcodes. This simple platform is far faster than using WordPress, and it is totally in your control. Tell me about your server in the comments below.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.