Dream is a nice Web framework that permits you to route your application calls between different rendering and processing functions. It comes with a templating system (but doesn't mandate it). It handles many use cases and is well documented with many use-case example. See Dream documentation page.
This first example assemble Dream and its templating framework, Caqti (a database library compatible with SQlite, Poastgres, MySql/MaraDB), and ppx_reaper (a preprocessor that makes it easier to use Caqti).
The main.ml page, runs the whole framework with a routing table.
let () =
Dream.run ~port:8181 ~interface:"0.0.0.0" (* ~interface:"127.0.0.1" *)
@@ Dream.logger
@@ Dream.sql_pool
("mariadb://login:password@localhost/test")
@@ Dream.sql_sessions ~lifetime:(200.*.86400.)
@@ Dream.set_secret "AZERTYUIKJNBVFCDS" (* Used by CSRF tokens *)
@@ Dream.router
[
Dream.get "/lost.html" @@ Dream.static file_path;
Dream.get "/" (fun request -> Dream.redirect request "/t1.html");
Dream.get "/t1.html" @@ render_t1;
Dream.post "/t1.html" @@ process_t1;
Dream.get "/login.html" @@ render_login;
Dream.post "/login.html" @@ process_login;
Dream.any "/logout.html" @@ render_logout;
Dream.any "/private" @@
(fun request -> Dream.redirect request "/private/index.html");
Dream.scope "/private" [ filter_admin ]
[
Dream.any "/**" @@ Dream.static (file_path ^ "/private");
];
Dream.any "/**" @@
(fun request -> Dream.redirect request "/lost.html")
]
We can see different route
handling. Dream.get, Dream.post
or any, match some file patterns and HTML queries,
and need an handler which is function that process
it. Some functions are already provided
(Dream.static for serving static files
and Dream.redirect for redirections), but we may use
a custom function.
Dream.scope handles a whole directory and needs a
list of middleware and another routing table. Usually,
an empty list of middleware is adequate, but here,
a filter_admin middleware enforce a security filter
at the whole directory.
The following handler prepare the data to be rendered and
calls Render_t1.render which does the actual
template rendering.
let ( let*? ) = Lwt_result.bind
let render_t1 request =
Dream.sql request (fun db ->
let*? table = get_t1 ~db in
let body =
Render_t1.render ~table ~csrf_tag:(Dream.csrf_tag request)
~logged:(is_logged request)
in
Lwt_result.return (Dream.html body))
|> process_caqti_error
We note that because of the SQL request get_t1, the
result must be a Lwt_result. Here let*?
allows you to chain easily multiple Lwt_result
(here, get_t1
and Lwt_result.return). The end result is processed
by process_caqti_error which render an error page if
the result is an error. It is defined by:
let process_caqti_error result =
match%lwt result with
| Ok x -> x
| Error error ->
let error = Caqti_error.show error in
Dream.log "Caqti:%s" error;
let body = Render_dberror.render ~error in
Dream.html body
The database query is defined by the simple query:
let get_t1 ~db =
[%rapper
get_many
{sql|
SELECT @int{id}, @string{value}
FROM t1
ORDER BY id
|sql}]
() db
Where each input parameters are tagged with tha
pattern @type{param}. The preprocessor ppx_rapper
transforms this string into a funtion that takes ()
db as parameters and returns a list of tuples (id,
value).
ppx_rapper has many options which are described on the ppx_rapper GitHub site.
The actual rendering is provided in a .eml file like:
let render ~table ~logged ~csrf_tag =
...
Private
% if logged then (
You are logged.
% ) else (
% );
T1 table
Value Actions
% List.iter (fun (id, value) ->
% ) table;
We notice the % ... form which interlace OCaml code
especially for control code (conditionals, loops), <%d var
%>, <%s var %> and <%s! var %>
for variable outputing. the ! is for raw output (no
HTML quoting) in cases we are sure not to mess the HTML structure
(XSS attacks...).