After learning erlang basics, and fell in love with it, one question pop up on my mind: Erlang is with no doubt a great language, but i make my living building web applications, so how i can use erlang to build web applications?
After some searching i discover 3 Erlang Web Frameworks : Nitrogen Project, ErlyWeb and ErlangWeb. I tested the 3, and i decided to go with Nitrogen, because it had some cool examples and documentation, and it looked simple. I have spend the last 6 months building web applications using nitrogen and i could not be more pleased.
This tutorial is divided into 3 parts, in part 1 we will assume that you are a newbie to erlang also, so i will try to explain some basic stuff about erlang as we go, but i recommend you to read this hello world tutorials to better understand this one #link , #link …
In part 2 we will show in a step by step explanation how to build a simple and powerful app with erlang and nitrogen, using twitter stream api.
In part 3 i will present you some erlang tools that will help you mastering erlang.
Get Started with Erlang & Nitrogen
Nitrogen is open source project, started in November 2008 by @rklophausunder MIT-LICENSE. Although its pretty stable, you should be ready to assume your risk using it on production environments.
All the following examples were made on Mac OS X but they should be valid to Linux and windows as well.
First lets check if you have erlang installed. Open a terminal and type erl, if you have an error you need to install erlang.
apt-get install erlang
or go here to download source. If you are seeing something like this, congratulations you have already erlang installed.
darkua:~ sergioveiga$ erl Erlang R13B (erts-5.7.1) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.7.1 (abort with ^G) 1>
Now its time to install Nitrogen, what is very easy, just follow this instructions, In resume you just need to clone nitrogen project into a directory that is included in erlang path($ERL_LIBS).
darkua:~ sergioveiga$ echo $ERL_LIBS /opt/local/lib/erlang/lib/:/usr/lib/erlang/lib/:/Users/sergioveiga/erlangProjects/tedi darkua:~ sergioveiga$ cd /opt/local/lib/erlang/lib/ darkua:lib sergioveiga$ git clone git://github.com/rklophaus/nitrogen.git
Now lets see if everthink is ok. Enter Nitrogen directory and run this command
./Quickstart/quickstart.sh
Now try open your browser on http://localhost:8000/web/index , you should see a copy of nitrogen website running on local.
Erlang Project Structure
More or less but all erlang project have a defined structure:
– ebin () – *.beam (code compiled) – name_of_the_application.app (application resource file) – include – *.hrl (structure descriptions to include) – src – *.erl (source code)application resource file is very important because it defines your application:
{application, name_of_the_application, [
{description, "App descritpion"},
{ vsn, "0.0.1" },
{mod, {name_of_the_application_app, []}},
{ applications, [ kernel, stdlib] },
{env, [
{platform, inets},
{port, 8000},
{session_timeout, 3600},
{sign_key, randomized},
{www_root, "./wwwroot"},
{templateroot,"./wwwroot/templates"},
{scratch_directory,'./wwwroot/scratch'}]},
]}.
This is an example of the app file, and you can read more about application behaviour here In resume its the file that describes you app to erlang vm, where you define its version, its environment vars, the modules, and the applications that your app will use.
Nitrogen as an extra dir called wwwroot, where we will store all our static files (js, html templates, css, images,etc…)
Now lets create your first project, using a nitrogen script that will do most of the work for you. Check if you have already nitrogen symbolic link created.
darkua:~ sergioveiga$ nitrogen Nitrogen Web Framework. Usage: nitrogen create [PROJECT_NAME] - Creates a project in a subdirectory nitrogen page [URL] - Create a page for URL (EXAMPLE: 'nitrogen page web/blog/post') darkua:~ sergioveiga$
if you get an error you need to create a symbolic link to the path you have previous clone nitrogen
sudo ln -s $ERL_LIBS/nitrogen/support/nitrogen \ /usr/local/bin/nitrogen
So now its time to create our first nitrogen project
nitrogen create twitter_stream
A twitter_stream directory has been created and you should see a directory structure close to the one, i described before. Now in order to see that everything is ok, just run in your shell the start script
./start.sh
Now you should be able to go to http://localhost:8000/ and see you project running. If you get this error
=ERROR REPORT==== 18-Nov-2009::19:25:31 ===
Failed initiating web server:
(...)
{listen,eaddrinuse}
(...)
This happens because you have already a server running on the same port. You need to shut down the other service, or change port on the .app file.
Now that we have our infrastructure ready lets get deep into real code
Nitrogen Workflow
Open twitter_stream.erl in the source directory
The most important in this page is understand the erlang behaviour concept. In resume Behaviours are formalizations of common patterns. So when we say -behavior(application), erlang expects this module to behave like an application. You can read more about it here. In this case the application behaviour just needs a start and a stop function. Now open start.sh script.
echo Starting TwitterStream. erl \ -name twitter_stream@localhost\ -pa ./ebin -pa ./include \ -s make all \ -eval "application:start(twitter_stream)"
In resume we are starting erlang vm, giving it a name, include and binary path, compiling the code, and start our application, that will execute the start method we saw on twitter_stream.erl. The rest for now its not important. So what happens when you go to http:localhost:8000/ ? If you check you twitter_stream.app file you will see this entries
{platform, inets}
{port, 8000}
This is very important because this defines what http server you are going to use, and in witch port is going to run. So nitrogen is not a web server, but it starts for you one of 3 possible web servers (inets, mochiweb, and yaws), you can read more about all of them, but for now we can use any. This also means that you dont need any other webserver (apache,etc…) to run your application
So when you enter the address the web server will receive the request and match it to a page. By default if you just call http://localhost:8000/ the request will be mapped to http://localhost:8000/web/index/ and this will run the web_index module inside /src/pages. So if you request /web/home it will execute the module web_home.erl inside the pages dir.
In order to avid to much detail, now lets assume that this mapping /web/xpto -> web_xpto.erl happens by magic
So now open the file web_index file.
-module (web_index).
-include_lib ("nitrogen/include/wf.inc").
-compile(export_all).
main() ->
#template { file="./wwwroot/template.html"}.
title() ->
"web_index".
body() ->
#label{text="web_index body."}.
event(_) -> ok.
-module (web_index).
The module definition is always required, and it defines your module name. Very important Reminder : erlang does not have namespaces, so try to use always prefixes on your own modules to avoid conflicts. For example dont call your module lists.erl… use for example my_lists.erl.. don’t laugh this happens to the best
-include_lib (”nitrogen/include/wf.inc”).
The way you can include a lib, in this case nitrogen lib, so you can use nitrogen stuff. Just keep it
-compile(export_all)
In order to call a function from module, you need to export it. To avoid that you can use this directive, but use it with caution, its best to export each one, like this:
-export([ main/0, body/0, title/0, event/0]).
So module, include, and exports is what we can call the “header” of the module. Bellow is where the code really begins
main() ->
#template { file="./wwwroot/template.html"}.
This function is needed in all your pages because its the one that will be executed when you request this page. This function needs to return (in erlang return is always the last line of execution) a #template element. In erlang we have a structure called record, read more here, and its similar to a struct in C. This records are used by nitrogen to create elements that will help you build web applications with erlang. The template element will define the html template for this page, and the file attribute is the location of your html page that will be used as template.
Open wwwroot/template.html.
As you can see its a perfect normal html page, with some weird stuff [[[page:title()]]] on it. As you probably are imagining right now, nitrogen will parse this page, and will replace the [[[page:title()]]] with the output of the title function inside web_index. The reason to use page, and not web_index is that you can use a template for many pages, and this way it will execute the title of each calling page.
The other important part is the [[[script]]] element. This is where nitrogen will put all javascript he creates. Just don’t remove it or you will get into troubles
Now back to web_index page, you can see that title function return a simple string, and body uses another nitrogen element. So mainly you have an element for all html elements (div, li, table,input…). The only problem is that nitrogen does not use always the same tags you find in html, so for example a span is #span element, but a div is #panel element… but don’t worry after a while you get use to it.
So lets try replace the body with this code:
body()->
#textbox{text="some text"},
#button{text="Hello!"}.
now go to erlang shell you have started the project and execute sync:go().
(nitrogen@localhost)2> sync:go(). Recompile: ./src/pages/web_index ./src/pages/web_index.erl:12: Warning: a term is constructed, but never used ok
sync:go() will turn into one of you best friends because it will compile all the files you have changed
You can see a warning, that is mainly telling that you have some code, that in never used, and if you go to the browser, and can see that you only have the button there. So what happen to the textbox? Like i told you before, erlang only returns the last code line, so mainly you are only returning the button. To return all you just need to do this:
body()->
[
#textbox{text="some text"},
#button{text="hello"}
].
Mainly you just need to add your elements to a list. In erlang [] is a list, not an array. Lists are on of the most used structures so bookmark this link, you will need it for sure.
Now sync again and go to the browser. You should see now a input text element and a button
Nitrogen has also a set of built in functions that do a lot of stuff for you. For example the wf:render one.
body()->
Elements = [
#textbox{id=input_id, text="some text"},
#button{text="hello"}
],
wf:render(Elements).
This does exactly the same as before but let you organize the code, the way you like most, what is always great.
So now that you know the alphabet lets start making words
Nitrogen Actions
The interaction between user and application, nitrogen calls it actions. So lets add some actions to our previous button.
pre #button{text=”hello”,actions=#event{type=click,postback=hello}} /pre
This will tell the button that on click, will execute the hello event.
So the next thing you need to do is the hello event
event(hello)->
io:format("~p~n",["test message"]);
event(_) -> ok.
sync:go(), and click on hello button and go back to your erlang shell.
You have just created your first client server interaction
. If you are using firebug you can easily see that this is done using an AJAX Event, where all the page info is send to the server, and matched to the specific event.
Now lets grab some info
event(hello)->
Input = wf:q("input_id"),
io:format("~p~n",[Input]);
sync:go(), and after clicking again go to erlang shell.
(nitrogen@localhost)1> ["some text"]
As you can see using nitrogen wf:q(id) function you have access to input box value. In resume you can access to all parameters sent in a get or post request. Lets test get parameters. Change your body function to this:
body()->
Input = wf:q("input_id"),
io:format("~p~n",[Input]),
Elements = [
#textbox{id=input_id, text="some text"},
#button{text="hello",actions=#event{type=click,postback=hello}}
],
wf:render(Elements).
sync:go(), and change url to http://localhost:8000/?input_id=hello
As you can see it work exactly the same, the important thing to remember is that wf:q can only read the header parameters of the request, so for example if now you click on the button, you are creating another request, and input_id=hello from the previous one does not exist.
Now as you can see the output comes inside a list, that we dont really want, so lets remove the element from the list. Replace Input with this:
hd(wf:q("input_id"))
sync:go(), and call the url again ( http://localhost:8000/?input_id=hello ). You should be able to see only the string with no list.
But now call the url without any parameter http://localhost:8000/
Say hello to your first of many erlang errors! Go back to the shell, and you will see exactly what happened
[{erlang,hd,[[]]},
{web_index,body,0},
{element_template,eval_callbacks,2},
{element_template,eval,2},
{element_template,eval,2},
{element_template,eval,2},
{element_template,eval,2},
{element_template,eval,2}]
Here you have info of witch module and function create the error {web_index,body,0}, and also witch instruction erlang,hd,[[]], and in resume the problem is that you are trying to get the head of an empty list.
In order to solve this we will introduce on of the most common structures in a erlang code, the case statement. As you probably have heard erlang does not have if statement, its false it has, but in the end you never need to use it, because the case really solves all your problems. So lets change again the Input :
Input = case wf:q("input_id") of
[]->
"Oops, not input!";
_->
hd(wf:q("input_id"))
end,
Case works like it is supposed to be, it will evaluate the return of wf:q and if it evaluates to [], what happens when you dont have the parameter defined, it will associate the string “Oops, not input!” to Input, or if it is something else (_) does not matter what, it will grab the head of the list.
Since erlang lives a lot of pattern matching, _ option is a powerful arm for your arsenal
Import reminder, and one of the most annoying stuff around erlang, is that the way you end your instruction (, or ; or .) its not a coincidence.
In resume, the end of a function is indicated by the dot (.), but if your function uses pattern matching with the same number of arguments, like it happens on event, only the last one ends with the (.) all the others end with (;).
event(hello)->
Input = hd(wf:q("input_id")),
io:format("~p~n",[Input]);
event(_) -> ok.
All other instruction always end with comma (,) with exception of case instruction where the different options are ended also with (;) and the last one does not need any, like you have seen in the previous example.
Back to the action itself, we were just sending an Atom (erlang simple structure, any word starting with a lower_case
), but we can send info in the postback. Replace postback with this:
postback={hello,"Hello, i'm string!"}
and adapt your event to receive the new info
event({hello,Item})->
io:format("~p~n",[Item]);
sync:go, and as should see how easy its to pass info into events.
Now that you have understood the basics around nitrogen its time to check more complex example, and the cool nitrogen elements, like the comet! So lets continue to part 2 of this tutorial, where we will build a twitter visualization using twitter stream api.