Library
Module
Module type
Parameter
Class
Class type
module Form : sig ... end
Typed, extensible HTML form decoder with error reporting for form field validation failures. Powerful chained decoding functionality–the validation of one field can depend on the values of other decoded fields.
val form :
'a Form.t ->
?csrf:bool ->
Dream.request ->
[> 'a Dream.form_result | `Invalid of (string * string) list ] Dream.promise
Type-safe wrapper for Dream.form
. Similarly to that, you can match on the result:
type new_user = { name : string; email : string }
let new_user =
let open Dream_html.Form in
let+ name = required string "name"
and+ email = required string "email" in
{ name; email }
(* POST /users *)
let post_users req =
match%lwt Dream_html.form new_user req with
| `Ok { name; email } -> (* ... *)
| `Invalid errors -> Dream.json ~code:422 ( (* ...render errors... *) )
| _ -> Dream.empty `Bad_Request
val query :
'a Form.t ->
Dream.request ->
[> `Ok of 'a | `Invalid of (string * string) list ]
Type-safe wrapper for Dream.all_queries
. Can be used to decode the query parameters into a typed value.
include module type of Pure_html
These are the types of the final values which get rendered.
val to_string : node -> string
val to_xml : ?header:bool -> node -> string
Same as to_string
but render void tags as XML-style self-closing tags.
val pp : Format.formatter -> node -> unit
val pp_xml : Format.formatter -> ?header:bool -> node -> unit
Same as pp
but render void tags as XML-style self-closing tags.
type 'a to_attr = 'a -> attr
Attributes can be created from typed values.
Special handling for string-value attributes so they can use format strings i.e. string interpolation.
A 'void element': https://developer.mozilla.org/en-US/docs/Glossary/Void_element with no children.
Tags which can have attributes but can contain only text. The text can be formatted.
val attr : string -> attr
attr name
is a new attribute which does not carry any payload. E.g.
let required = attr "required"
val string_attr : string -> ?raw:bool -> _ string_attr
string_attr name fmt
is a new string-valued attribute which allows formatting i.e. string interpolation of the value. Note, the fmt
argument is required due to the value restriction.
val uri_attr : string -> _ string_attr
Convenience for attributes whose values should be URIs. Takes care of both URI-encoding and attribute escaping, as recommended in https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#common-mistake.
Examples
a [href "/blog?tags=iamsafe\"></a><script>alert('Pwned')</script>"] [txt "Tags: tag1 | tag2"]
==>
<a href="/blog?tags=iamsafe%22%3E%3C/a%3E%3Cscript%3Ealert('Pwned')%3C/script%3E">Tags: tag1 | tag2</a>
a [href "/foo?a=1&b=2 3&c=4<5&d=6>5"] [txt "Test"]
==>
<a href="/foo?a=1&b=2%203&c=4%3C5&d=6%3E5">Test</a>
val bool_attr : string -> bool to_attr
val float_attr : string -> float to_attr
val int_attr : string -> int to_attr
val std_tag : string -> std_tag
val void_tag : string -> void_tag
val text_tag : string -> ?raw:bool -> _ text_tag
Build a tag which can contain only text.
val uri_tag : string -> _ text_tag
Build a tag which can contain only a URI. The URI is escaped with the same rules as a uri_attr
.
A text node inside the DOM e.g. the 'hi' in <b>hi</b>
. Allows string interpolation using the same formatting features as Printf.sprintf
:
b [] [txt "Hello, %s!" name]
Or without interpolation:
b [] [txt "Bold of you."]
HTML-escapes the text value. You can use the ~raw
param to bypass escaping:
let user_input = "<script>alert('I like HTML injection')</script>" in
txt ~raw:true "%s" user_input
val comment : string -> node
A comment that will be embedded in the rendered HTML, i.e. <!-- comment -->
. The text is HTML-escaped.
concat node list
is the list
of nodes joined together into a single node, with each element separated by node
.
Add an attribute to a tag.
let toast msg = p [id "toast"] [txt "%s" msg]
let toast_oob = toast "ok." +@ Hx.swap_oob "true"
val (.@[]) : node -> string -> string
Get the value of an existing attribute.
let toast = p [id "toast"] [txt "OK."]
let toast_id = toast.@["id"]
val is_null : node -> bool
Get whether a node is null (empty) or not. Useful for conditional rendering of UIs when you are passed in a node and you don't know if it's empty or not.
val is_null_ : attr -> bool
Get whether an attribute is null (empty) or not.
module HTML : sig ... end
All standard HTML attributes and tags. Some attributes and tags have the same name, e.g. style
. To disambiguate them, attributes have a _
(underscore) suffix.
module SVG : sig ... end
module MathML : sig ... end
module Aria : sig ... end
module Atom : sig ... end
val respond :
?status:[< Dream.status ] ->
?code:int ->
?headers:(string * string) list ->
node ->
Dream.response Dream.promise
val send :
?text_or_binary:[< Dream.text_or_binary ] ->
?end_of_message:[< Dream.end_of_message ] ->
Dream.websocket ->
node ->
unit Dream.promise
Type-safe wrapper for Dream.send
.
val set_body : Dream.response -> node -> unit
Type-safe wrapper for Dream.set_body
. Sets the body to the given node
and sets the Content-Type
header to text/html
.
val write : Dream.stream -> node -> unit Dream.promise
Type-safe wrapper for Dream.write
.
val csrf_tag : Dream.request -> node
Convenience to add a CSRF token generated by Dream into your form. Type-safe equivalent of Dream.csrf_tag
.
form
[action "/foo"]
[csrf_tag req; input [name "bar"]; input [type_ "submit"]]
Bidirectional paths with type-safe path segment parsing and printing using OCaml's built-in format strings, and fully plug-and-play compatible with Dream routes.
type ('r, 'p) route = ('r, 'p) path -> (Dream.request -> 'r) -> Dream.route
Wrapper for a Dream route that represents the ability to parse path parameters and pass them to the handler function with the correct types.
val path :
('r, unit, Dream.response Dream.promise) format ->
('p, unit, string, attr) format4 ->
('r, 'p) path
path request_fmt attr_fmt
is a router path. The dream-html.ppx
provides a more convenient way.
Without PPX: let order = path "/orders/%s" "/orders/%s"
With PPX: let%path order = "/orders/%s"
Refer to the PPX documentation for instructions on using it.
⚠️ Due to the way Dream's router works, all parameter captures happen between /
characters and the end of the path (or the ?
character, whichever comes first). Eg, /foo/%s/bar/%d
is valid, but /foo/%s.%s
(note the dot character) is not a valid capture.
⚠️ If a route is matched but the data type does not match, a 400 Bad Request
response will be returned. The following type conversion specs are supported:
%s
capture a string
and pass it to the handler
%*s
capture the rest of the path and pass the captured length and string to the handler
%c
capture a char
%d
or %i
capture an int
%x
capture a hexadecimal int
%X
capture an uppercase hexadecimal int
%o
capture an octal int
%ld
capture an int32
%Ld
capture an int64
%f
capture a float
%B
capture a bool
⚠️ We are actually using Dream's built-in router, not our own, and Dream's router doesn't distinguish between parameter types. So, to Dream both /%s
and /%d
are the same path. It will route the request to whichever happens to be first in the route list, and that one will succeed or fail depending on its type and the request target.
val path_attr : 'p string_attr -> (_, 'p) path -> 'p
path_attr attr path
is an HTML attribute with the path parameters filled in from the given values. Eg,
let%path order = "/orders/%s"
open Dream_html
open HTML
a [path_attr href order "yzxyzc"] [txt "My Order"]
Renders: <a href="/orders/yzxyzc">My Order</a>
Use this instead of hard-coding your route URLs throughout your app, to make it easy to refactor routes with minimal effort.
pp_path
is a pretty-printer for path values. For a path like path "/foo" "/foo"
, it will print out /foo
.
val get : (_, _) route
Type-safe wrappers for Dream.get
and so on. Using the PPX, eg:
let%path order = "/orders/%s"
let get_order = get order (fun request order_id ->
...
a [path_attr href order order_id] [txt "Your order"]
...
)
val post : (_, _) route
val put : (_, _) route
val delete : (_, _) route
val head : (_, _) route
val connect : (_, _) route
val options : (_, _) route
val trace : (_, _) route
val patch : (_, _) route
val any : (_, _) route
val use : Dream.middleware list -> Dream.route list -> Dream.route
use middlewares routes
is a route that is composed of all the given routes
with the middlewares
attached to them.
val static_asset : (Dream.response Dream.promise, _) path -> Dream.route
static_asset path
is a route that handles serving the static file at the path
. Importantly, it sets an immutable cache header which remains valid for a year.
⚠️ Be sure that the resource has a unique identifier because it will be cached immutably. The dreamwork
CLI tool automates this for you. See Dreamwork.
TL;DR:
dreamwork setup
static/assets/
subdirectory with filesdune build
Static.routes
to the app's main Dream routerimg [path_attr src Static.Assets.icon_png]
which will render with a revision hash based on the file contents, calculated at build timedreamwork
is a CLI tool that helps set up and manage static file paths and routes with proper content-based version hashes. The static files will live inside a dune component called static
and in the static/assets
subdirectory. Suppose you have the following directory tree:
static / dune assets / css / app.css js / app.js
The dune
file defines a library
component that will make the following module available:
module Static : sig
val routes : Dream.route
(** This route will serve all of the following paths. *)
module Assets : sig
module Css : sig
val app_css : (Dream.response Dream.promise, attr) path
end
module Js : sig
val app_js : (Dream.response Dream.promise, attr) path
end
end
end
So, you can just stick Static.routes
in your router and it will correctly serve the files from the filesystem with an immutable cache of 1 year; and you can use Static.Assets.Css.app_css
and so on in your dream-html markup code and it will correctly render with a ?rev=...
query parameter that uniquely identifies this revision of the file with a content-based hash for cache-busting purposes:
link [rel "stylesheet"; path_attr href Static.Assets.Css.app_css
(*
<link
rel="stylesheet"
href="/static/assets/css/app.css?rev=17fb8161afc85df86156ea1f3744c8a2"
>
*)
script [path_attr src Static.Assets.Js.app_js] ""
(*
<script src="/static/assets/js/app.js?rev=677645e5ac37d683c5039a85c41c339f">
</script>
*)
You control the directory subtree under assets
; the dreamwork
CLI just helps you define the dune
component that generates the above module structure. The module structure mirrors the directory tree structure.
The entry point to dreamwork
is the dreamwork setup
command, which creates static/
, assets/
, and dune
. In the dune
file it defines a code generation rule which uses the dreamwork static
command to generate the OCaml code.
So, you just need to run dreamwork setup
to initialize the directory structure and code generation. After that, you can add and remove any files inside assets/
as you want and on the next dune build the Static
module structure will be updated accordingly.
module Livereload : sig ... end
Live reload script injection and handling. Adapted from Dream.livereload
middleware. This version is not a middleware so it's not as plug-and-play as that, but on the other hand it's much simpler to implement because it uses type-safe dream-html nodes rather than parsing and printing raw HTML. See below for the 3-step process to use it.