Embedding the Nitrogen Web Framework into an Existing Erlang Application

by jesse
2013-08-07

Up until very recently, adding Nitrogen to an existing application was an exercise in frustration, even for Erlang veterans. This is no longer the case, adding Nitrogen to an existing application is now a snap with the new embed helper script.

Originally, If you were starting from scratch and making a Nitrogen-focused app, it was simple, just run make rel_XXX or make slim_XXX) (where XXX is the backend of choice). But if you had an already existing application and you wanted to add a web front-end or web interface and use Nitrogen, then you had to follow a bunch of steps, copying files from one repository, changing paths in files, adding dependencies - in short, it was a pain.

Enter embed

The new embed script was just added to the root of what will soon be Nitrogen 2.2.0.

This script will do all the heavy lifting for you, asking your preferences, automatically merging dependencies, adding Makefile rules, and copying the necessary files for the backend of your choice.

With this script, the only manual changes you'll have to are to your launch script: Add the necessary code paths, config file loading, and either making the call to nitrogen_sup:start_link(), or simply including that into your supervisor tree.

Ninja-quick version

  1. Clone nitrogen
  2. Navigate to the root of the application you wish to add Nitrogen to
  3. Run /path/to/nitrogen/embed
  4. Follow on-screen instructions`
  5. Modify your launch script to include -pa deps/nitrogen_core/ebin, -config path/to/file.config for each config file, and a call to -eval "nitrogen_sup:start_link()" (or add it to your supervision tree)
  6. make (if your makefile does everything necessary), or rebar get-deps followed by rebar compile.
  7. Run application
  8. Navigate browser to http://127.0.0.1:8000

Detailed Demonstration

For a simple demonstration, let's do something completely contrived. Let's add a web interface to qdate, the date and timezone utility.

First, we clone qdate and Nitrogen.

$ git clone git://github.com/choptastic/qdate.git
$ git clone git://github.com/nitrogen/nitrogen.git

Now we enter the qdate directory, and to be safe, let's make a 'web-interface' branch

cd qdate
$ git checkout -b web-interface

Now we make the call to embed. Currently, we must make sure to make the call from the directory we wish to install to. So we call it like this (I'm just including the pwd call to demonstrate our current location).

$ pwd
/home/user/qdate
$ ../nitrogen/embed

Which will result in you seeing this:

**************************************************************
****           NITROGEN WEB FRAMEWORK EMBEDDER            ****
****                                                      ****
****   Adding Nitrogen to an existing Erlang Application  ****
****                                                      ****
**************************************************************

NOTE: BEFORE PROCEEDING IT'S HIGHLY RECOMMENDED THAT YOU MAKE
      NEW BRANCH IN YOUR SOURCE CODE MANAGEMENT SYSTEM, OR AT
      LEAST MAKE A BACKUP OF YOUR DIRECTORY, AS THIS WILL MAKE
      CHANGES TO THE CURRENT DIRECTORY TREE BY ADDING AND
      MODIFYING FILES.


Will be installing from '/home/user/nitrogen' into '/home/user/qdate'

Web server to use?
Options are (c)owboy, (i)nets, (m)ochiweb, (w)ebmachine, (y)aws.
Please choose (i/m/c/w/y): 

Here you enter your choice: c, i, m, w, or y For our demonstrating we'll choose cowboy, so we type c and press enter

Now we are presented with a series of path questions, with sensible defaults provided. Pressing "Enter" will stick with the default, or you can type the updated path (paths will be relative to the current working directory).

Where to put the Erlang code for Nitrogen pages and initialization [Default: src/nitrogen/]: 
Where to put the Erlang headers [Default: include/]: 
Where to put the static directory (for js, css, etc) [Default: priv/static/]: 
Where to put the templates directory [Default: priv/templates/]: 
Where to put the config files [Default: etc/]: 

After the paths are provided, you're presented with a series of yes/no questions:

Install the plugin scripts and configs? (y/n): y  
Add Nitrogen dependencies to rebar.config automatically? (nitrogen_core, simple_bridge, nprocreg, sync, cowboy, and any of cowboy's dependencies. (y/n): y
Add a 'make plugins' rule to the Makefile? (y/n): y
Add a 'make copy-static' rule to the Makefile? (y/n): y
Run 'make' after installation? (y/n): y

Note: Unless you want to do something manually, it's recommended to choose "y" for all provided questions.

Finally, we get down to it. The embed script presents us with our chosen preferences, and a final "Are you sure you want to proceed?" question.

************************************************
*** We're almost ready to go. Please review: 
*** Erlang Source: src/nitrogen/
*** Erlang Headers: include/
*** Static Resources: priv/static/
*** Nitrogen Templates: priv/templates/
*** Config Files: etc/
*** Install Plugin Script: Yes
*** Add 'make plugins' rule to Makefile: Yes
*** Add 'make copy-static' rule to Makefile: Yes
*** Add dependencies to rebar.config: Yes
*** Run 'make' after all is complete: Yes
************************************************

Are you sure you want to proceed? (y/n): 

Entering y and pressing Enter will begin the installation process.

After fast-scrolling informative text (telling you what the script is doing, followed, if you specified it, by the running of make), you'll be presented with the following summary with some instructions:

*******************************************************************************
****                          Installation Complete                        ****
*******************************************************************************
There are a few manual steps you must take before you're completely set up:

1) You must make sure to start Nitrogen.  The easiest way is by
    adding `nitrogen_sup:start_link()` to your application.

2) You must make sure that the config files in etc/ are properly
   loaded. This can be done with a vm.args file if you're using a reltool
   generated release, or it can be done by adding additional -config calls to
   the commandline call that launches your app.

3) You may need to add the new packages (nitrogen_core, simple_bridge,
   nprocreg, etc) to your app's code path (the easy way is with the -pa flag
   in the `erl` call.)


Congratulations on adding Nitrogen to your Application

As it says, we're almost there. Now we just need to make some changes to the launch script:

Let's edit Makefile and make a change. Currently, make run is the quick way to launch a qdate terminal, so let's edit that run rule in the Makefile.

Original:

run:
    erl -pa ebin/ deps/*/ebin/ -eval "application:start(qdate)"

Let's load some configs, and add a call to start nitrogen:

run:
   erl -pa ebin/ deps/*/ebin/ -config etc/cowboy.config -config etc/app.config -eval "application:start(qdate)" -eval "nitrogen_sup:start_link()"

(Note: we didn't need to modify the path, since -pa deps/*/ebin was already in there, which covers all necessary dependency paths for us.)

Finally, let's launch qdate and see if we get the default Nitrogen page:

$ make run
erl -pa ebin/ deps/*/ebin/ -config etc/cowboy.config -config etc/app.config -eval "application:start(qdate)" -eval "nitrogen_sup:start_link()"
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> Starting Cowboy Server (nitrogen) on 0.0.0.0:8000, root: './priv/static'

Perfect! Let's see what happens when we navigate our browser to http://127.0.0.1:8000

WDefault Nitrogen Index page

Great! It's also loading the default "Welcome to Nitrogen" page.

Let's put it all together now

The last step is to simply add a web interface for qdate.

So let's delete the unnecessary 'mobile.erl' pages from src/nitrogen (our sensible default) and we'll just make a simple change to index.erl.

cd src/nitrogen
rm index.erl

Let's change it to this:

-module (index).
-compile(export_all).
-include_lib("nitrogen_core/include/wf.hrl").

main() -> #template { file="./priv/templates/bare.html" }.

title() -> "Welcome to qdate".

body() ->
    #container_12 { body=[
        #grid_8 { alpha=true, prefix=2, suffix=2, omega=true, body=inner_body() }
    ]}.

inner_body() ->
    [   
        #h1 { text="Welcome to qdate" },
                #label{text="Enter a free-form date and time here"},
                #textbox{id=source_date, text=qdate:to_string("l, F jS, Y g:i A T")},

                #label{text="Enter a new format string"},
                #textbox{id=format_string, text="Y-m-d g:ia"},

                #label{text="Enter a timezone to convert to"},
                #textbox{id=timezone, text="America/Chicago"},

                #br{},

                #button{text="Convert", postback=convert},
                #panel{text="", id=result}
    ].

event(convert) ->
        [From, String, TZ] = wf:mq([source_date, format_string, timezone]),
        wf:update(result, qdate:to_string(String, TZ, From)).

And recompile (we can enabled sync, if you so desire, or just kill the server and run make). After compiling and loading the modules, we get this:

Welcome to qdate is working!

We can enter a source date, a new format, and a timezone, and it'll do the conversion for us using qdate.

Conclusion

While this example is pretty contrived, it gives us an idea of just how much easier it is to add Nitrogen to an application now without having to jump through a bunch of hoops, or a series of tubes, if you will.