Rapi::Blog::Manual - Rapi::Blog user and developer manual


Rapi::Blog is a turn-key, customizable blog platform written using RapidApp.


Rapi::Blog can be installed from CPAN in the usual manner:

  cpanm Rapi::Blog

It is also distributed with the RapidApp Docker images on Docker Hub, specifically the rapi/psgi image which contains the full stack as of version 1.3001-B. You can pull this image to any system with docker installed by running this command:

  docker pull rapi/psgi


Rapi::Blog is a Plack app and can be ran directly (See the SYNOPSIS in Rapi::Blog). However, for most cases, setting up a new blog can be done using the included utility script create path/to/my-cool-blog

Where "my-cool-blog" is a directory which does not exist (or is empty). This script will start a wizard which will allow you to choose from one of the built-in scaffolds to use as a starting point. This will also give you a chance to set a custom password for the admin user (or use the default init from RapidApp which is pass).

The script will create the databases and files within the directory you choose, including an app.psgi file which you can use to plackup the site.

The public content will be copied into the scaffold directory from the built-in skeleton scaffold you selected. Depending on the one you chose (only a couple choices as of version 1.0), this will provide a fully working blog out-of-the-box. You can then replace/change any of the html files for your own needs within the scaffold, which is just an ordinary html site, with access to call template directives.

Creating with Docker

In order to create a new site using Docker and the RapidApp rapi/psgi image, you simply need to create a new container and then run the script from witin it.

First, create the directory you want to use for the site, then create and start a new container. For this example, we'll use port 5001 to run the app:

  # Manually create the site directory:
  mkdir path/to/my-cool-blog
  # Create the new container:
  docker create -it \
    --name=my-cool-blog --hostname=my-cool-blog \
    -p 5001:5000 \
    -v $(pwd)/my-cool-blog:/opt/app \
  # Start the container:
  docker start my-cool-blog

Since the app directory is empty, the app will start in a shutdown state. Next, open a shell on the running container and run the create script using the special /opt/app directory as the path:

  docker exec -it my-cool-blog bash
  # Now from the shell of the docker container, create the site: create /opt/app

Once the site is created, you can either restart the docker container, or run the provided app-restart command from the container shell. Now that the app directory is populated, the container will start normally going forward.

For more information, see the rapi/psgi documentation on Docker Hub:


Rapi::Blog serves public facing content from a local "scaffold" which is a simple directory of HTML and associated content, mounted at the root / of the app. The other, backend namespaces for the various controllers/modules are merged into the common root-based namespace. This design allows for the scaffolds to be structured as an ordinary HTML site following the same conventions as a static site living in a folder on your PC. The only difference between a Rapi::Blog scaffold and a static HTML directory is that files within the scaffold are able to *optionally* call template directives for dynamic content via simple variable substitution.

Scaffolds may also define an additional config which declares details about how it can be used by the backend. See below.


The scaffold config is defined in a YAML text file named scaffold.yml in the root of the scaffold directory.

The scaffold.yml supports the following options:


List of path prefixes which are considered 'static' meaning no template processing will happen on those paths. You want to list asset/image dirs like 'css/', 'fonts/' etc in this param, which helps performance for paths which the system doesn't need to parse for template directives.


List of path prefixes which will not be served publicly, but will still be available internally to include, use as a wrapper, etc. Note that this option isn't really about security but about cleaning the public namespace of paths which are known not to be able to render as stand-alone pages in that context (like snippets, block directives, etc etc)


Path/template to render when a user lands on the home page at '/' (or the URL the app is mounted on)


Path/template to render when a requested path is not found (404). This provides a mechanism to have branded 404 pages. If not specified, a default/plain 404 page is used.


Path to the favicon to use for the site.


The view_wrappers are what expose Posts in the database to public URLs. The view_wrappers are supplied as a list of HashRefs defining the config of an individual view_wrapper. For example:

    - { path: post/,  type: include, wrapper: private/post.html  }

For typical setups, only one view_wrapper is needed, but you can setup as many as you choose.

Each 'view_wrapper' config requires the following three params:


The path prefix to be exposed. The Post name is appended to this path to produce a valid URL for the given Post. Using the above example, a Post named 'my-cool-post' would be accessible at the public URL '/post/my-cool-post'.


The scaffold template to use when rendering the Post. This should be a TT template which has been setup as a WRAPPER with a the Post body content loaded in [% content %]. The given Post row object will also be available as [% Post %].


Must be either 'insert' or 'include' to control which TT directive is used internally to load the Post into the [% content %] in the wrapper. With 'include' (default) the content of the template, along with its template directives, is processed in advance and then populated into the [% content %] variable, while with 'insert' the content is inserted as-is, leaving any directives to be processed in the scope of the wrapper. You shouldn't need to use 'insert' unless you have a specific reason and know what you are doing.


The default 'view_wrapper', identified by its configured path, to use for accessing posts. This is used in locations where the system generates URLs when it needs to publicly link to a post. Defaults to the first 'include' type view_wrapper.


Optional alternative path to use instead of default_view_path for the purpose of rendering a "preview" of the post as it will display on the site. This preview is presented in an iframe on the "Publish Preview" tab on the internal Post row page. This is useful in case you want to use a different wrapper that excludes items like top navigation, sidebars, etc, and display just the post itself with the active styles on the site. It is totally optional, and for some cases you may prefer using the same exact view as the public site to be able to really see exactly how the post will look. This would be one of the common a cases for a second 'view_wrapper' to be defined.


The one, "real" URL path used to access Posts *internally*. This is used by the view_wrappers internally to resolve the Post from the supplied URL. Defaults to 'private/post/' which shouldn't normally need to be changed. It is suggested that it be within a private (i.e. covered by 'private_paths') namespace.


Default file extension for scaffold templates, defaults to html. This makes it so that the template '/path/to/doc.html' can alternatively be accessed at '/path/to/doc' for cleaner/nicer looking URLs

User-defined scaffold datapoints

You may also define any additional datapoints you like in the scaffold config which can be used by the scaffold templates internally. The built-in scaffolds define custom datapoints such as "pages" which they use internally to build menus, etc. The scaffold can be used as a general-purpose meta data store which is available in templates via the [% scaffold %] template variable.


As of the current version of Rapi::Blog, besides Author, there are three other general purpose, built-in (i.e. defined by the default database schema) taxonomies, Tags, Categories and Sections. Tags and Categories operate as many-to-many links, meaning Posts can have multiple Tags and be in multiple Categories, with the difference between them being the manner in which they are set in the admin interface (see below), and Sections are single FK relationships, meaning a Post can be in exactly one Section (or none).


Tags are created automatically by using social-media syle hashtags in the Post body. For example, if you include the string #foo the Post will have the tag "foo," if you include the string #CodingStuff the Post will have the tag "codingstuff" (names are normalized lower-case). The first time a tag name is seen a new Tag row (which is a first class object) is created automatically.


Categories must be setup in advance and then can be selected when created or editing a Post. When no Category rows exist, the category selector/field is not shown on Post add/edit screens, meaning if you don't want to use Categories, just don't create any.


Like Categories, Sections must be set up in advance and are also not required. If you don't create any sections, the selector/field will not be shown when creating Posts. Unlike Catagories and Tags whith are many-to-many, Sections are single relationships so a Post can only have one Section. However, Sections can be divided into a hierarchy of parent/child sections, meaning a given Section may be a sub-section of another, which in turn may be a sub-section, and so on (currently depth is limited to 10 levels by rule, and circular references are not allowed). While sub-sections may be defined, this is also not required, and all Sections could be created on the top-level (i.e. not setting any parents).

These choices are are left intentionally ambiguous to give maximum flexibility to the user/scaffold to use the taxonomies they want, and not use the ones the don't want.


Files within the scaffold (which are not defined as static_paths) are processed as Template::Toolkit templates with the following methods/parameters exposed in the template variables (i.e. the Template::Stash):


The active scaffold config/data structure.


Interface to retrieve and filter Posts from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.

Accepted params (all optional):

String to search/filter results. The search will match substrings in the body, name, title, summary, body and exact match on a tag. Only Posts with at least one match will be returned.


Limit posts to those containing the named tag.


Limit posts to those containing the named category.


The maximum number of results to return (i.e. page size).


The page number to return, in conjunction with limit.


Sort keyword for the order posts are returned in. Currently supports 3 possible values (defaults to newest)


Newest (by Post Date/Time) Posts first


Posts with the most "Hits" first. Note that in order for a hit to be recorded, the scaffold must call the template directive [% Post.record_hit %].


Posts with the most comments (includes sub-comments/replies) first


Interface to retrieve and filter Tags from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.

Accepted params (all optional):


String to search/filter results. The search will match substrings of the tag name.


Limit results to Tags linked to the supplied post_id


Sort keyword for the order tags are returned in. Currently supports 3 possible values (defaults to popularity)


Most popular tags by number of posts which use them.


Tags in alphabetical order.


Most recently used (according to Post Date/Time) tags first.


Interface to retrieve and filter Categories from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.

Accepted params (all optional):


String to search/filter results. The search will match substrings of the category name.


Limit results to Categories linked to the supplied post_id


Sort keyword for the order tags are returned in. Currently supports 3 possible values (defaults to popularity)


Most popular categories by number of posts which use them.


Categories in alphabetical order.


Most recently used (according to Post Date/Time) categories first.


Interface to retrieve and filter Users from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.

Accepted params (all optional):


String to search/filter results. The search will match substrings of the username or the full_name.


Limit users to those with the matching named permission, which are currently 'authors' or 'commenters'


The currently logged in user, or undef if not logged in. Returns a Rapi::Blog::DB::Result::User object.


The path of the current request


URL path that a client can use to access the Remote controller which provides additional API end-points that can be used to post comments, etc.


URL path to directly load the "New Post" form. Useful if the scaffold wants to provide its own "New Post" links(s).


Same as [% c.mount_url %] - from RapidApp, the mounted path of the app if it is at a sub-url (e.g. using mount with Plack::Builder, etc). If the app is mounted normally on / this will be an empty string ''.

This is provided as a top-level variable so non-privileged templates can access it (since they do not have access to the actual context object in [% c %].


When the target template of the request is a Post, including requests dispatched via a view_wrapper, 'Post' template variable will also be available. This will be a reference to the associated Post Row object (see Rapi::Blog::DB::Result::Post). For non-post requests (i.e. requests to ordinary templates within the scaffold) this value will be undef.


Both scaffold templates as well as post templates are able to access the above template directives/attributes, however, additional privileged directives are available to scaffold templates only (excludes view_wrappers which are processed in the role of a post)


A reference to the Catalyst context object for the current request. This provides an entrypoint to the entire application which can be used to perform actions based on the query string params (e.g. [% %], direct access to the database (e.g. [% c.model('DB::Post').search_rs(...) %]) or anything else which can be reached via the context.


Rapi::Blog was designed from the ground up to support automatic schema migrations for all publically released versions of the built-in Schema. This is done via scan/fingerprint of the schema of the existing database to identify the known/previous schema version, and running the all DDL (i.e ALTER TABLE, etc) needed to upgrade it to the current version. When the database does not match any known fingerprints (including the fingerprint of the current version) the app will refuse to start.

Whenever a database is upgraded, a backup of the old database file is made and saved in the directory .bkp.rapi_blog.db/ within the site_dir.

This means that under normal circumstances, you don't need to worry about upgrading sites from previous versions of Rapi::Blog -- it should just work and automagically do the right thing.