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.

New in v1.13 - in addition to prefixes and literal paths, standard glob wildcard patterns are also supported.


New in v1.13.

Works in conjunction with static_paths but is inclusive rather than exclusive. If a set of template_names is defined, only scaffold templates which match will be treated as active/dynamic, the rest will be treated as static. Unlike static_paths which apply to public, URL paths, the template_names match the actual, post-translated name.

For instance, when a default_ext is defined, as is typical and is usually 'html' the public path '/list' actually resolves to '/list.html' and it is the latter which is considered by the template_names parameter. So, for instance, if you set '*.html' as the only entry in template_names, it would mean that all files which do not end in .html will be static, but /list would still render as an active template because its effective path is /list.html which does match the rule.


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)

New in v1.13, these also now support standard glob wildcards.


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):


A HashRef of key/value pairs matching the params of the Rapi::Blog builder object. This is the same as the priviledged template variable c.ra_builder except instead of being the actual object instance, it is just a copy of public attribute values. This allows templates access to the Rapi::Blog constructor params and computed settings without having to expose the actual live object which is super-user access.


The active scaffold config/data structure. Note: since the addition of multiple layered scaffolds, this object represents the effective, merged config of all active scaffolds.


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


Exactly the same as list_posts except the 'body' column is not excluded from the returned rows. The default list_posts excludes the body for performance, since it is typically not needed when fetching a list of posts. get_posts should only be used when the body of each post is needed.


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 %].


The external URL actually being used to access the site (experimental). This is actually not as simple as it may seem, as different deployment setups can change the environment from which this info can be determined. The intent is to provide the same base URL that the client browser is currently using to access the site. This is useful when dynamically generating public links, and you do not want to have to know/hard-code the site's domain/URL.


Access to arbitrary data that is specific to both the current session as well as the current URL/path of the active request.

This is used in certain Remote controller calls, such as password_reset, which involve a UI lifecycle that needs to span several requests and allow the template to display messages to the user. This is being set according to the API of the various features which use this, but can also be passed a value from the template call to be set as well.

This also supports the special value 'clear' which can be supplied which will clear/delete the value after returning it.


When reCAPTCHA support is enabled via defining a valid recaptcha_config, this returns the HTML script tag which must be included in client side HTML to enable the reCAPTCHA dialog on the page.


When reCAPTCHA support is enabled via defining a valid recaptcha_config, AND the required recaptcha_script_tag has been included in the page/template, the recaptcha_form_item provides the actual HTML element which presents the "I am not a robot" dialog/checkbox. This is typically inserted just before the "Submit" button of a form.

Note: for this to do anything, the page the form submits to must also support the reCAPTCHA v2 API. Currently this support is built in on the signup and email_login (AKA forgot_password) endpoints of the Remote controller. Additionally, the built-in client-side templates for both of these are also configured.

Also note that these templates are designed such that the reCAPTCHA dialog only appears if as recaptcha_config has been defined, and since this requires per-site registration with Google, this is not (and cannot be) enabled by default. For more information, refer to the Google recaptcha_config v2 documentation.


When performing final rendering of a post, the body content is processed by a Post-processor, which is simply a Template::Toolkit processor class. The default Post-processor is Rapi::Blog::Template::Postprocessor::MarkdownElement which injects a script include and wraps the content to be processed on the client-side browser using the marked.js library.

This post-processing only happens during specific dispatch flows and on the Post body, however, it can also be called interactively on any arbitrary content by using the ppRender directive. This must be called with both the name of the post-processor and the content to be processed. For example:

  [% ppRender.MarkdownElement(content) %]

There is a new post-processor class now available called TextMarkdown which processes markdown content using the perl Text::Markdown package:

  [% ppRender.TextMarkdown(content) %]

Text::Markdown is an older library and doesn't support as many features as marked.js which is why the latter is the default. However, this does come with the limitation that it relies on client-side javascript. In cases where this isn't available, such as RSS feeds or other repost scenarios, the TextMarkdown post-processor is available.

The ppRender directive supports several different call forms. The following are all equivalent:

  [% ppRender.TextMarkdown(content) %]
  [% ppRender('TextMarkdown',content) %]
  [% ppRender('Rapi::Blog::Template::Postprocessor::TextMarkdown',content) %]

The base namespace Rapi::Blog::Template::Postprocessor:: is used by default, but full package names can also be supplied so user-defined post-processors can be used.


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.


Whenever called, if a user is already logged in, they are logged out in the background. This can be placed on any pages or in any templates where ensuring a user is not logged in is desired, such as on sign up pages, or other screens where it doesn't make sense for a user to be logged in.


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.