Sinatra like web toolkit for OCaml
782
stars
840
commits
OCaml
primary language
Jan 6, 2026
updated
Since version 0.19.0, Opium uses httpaf. The last version that used Cohttp can be found at https://github.com/rgrinberg/opium/tree/0.18.0
Sinatra like web toolkit for OCaml based on httpaf & lwt
Opium should be very small and easily learnable. A programmer should be instantly productive when starting out.
Opium should be extensible using independently developed plugins. This is a
Rack inspired mechanism borrowed from Ruby. The middleware mechanism in
Opium is called Rock.
The latest stable version is available on opam
$ opam install opium
$ opam pin add rock.~dev https://github.com/rgrinberg/opium.git
$ opam pin add opium.~dev https://github.com/rgrinberg/opium.git
For the API documentation:
odig:
odig doc opium.The following tutorials walk through various usecases of Opium:
For examples of idiomatic usage, see the ./example directory and the simple examples below.
Assuming the necessary dependencies are installed, $ dune build @example will
compile all examples. The binaries are located in _build/default/example/.
You can execute these binaries directly, though in the examples below we use
dune exec to run them.
Here's a simple hello world example to get your feet wet:
$ cat hello_world.ml
open Opium
module Person = struct
type t =
{ name : string
; age : int
}
let yojson_of_t t = `Assoc [ "name", `String t.name; "age", `Int t.age ]
let t_of_yojson yojson =
match yojson with
| `Assoc [ ("name", `String name); ("age", `Int age) ] -> { name; age }
| _ -> failwith "invalid person json"
;;
end
let print_person_handler req =
let name = Router.param req "name" in
let age = Router.param req "age" |> int_of_string in
let person = { Person.name; age } |> Person.yojson_of_t in
Lwt.return (Response.of_json person)
;;
let update_person_handler req =
let open Lwt.Syntax in
let+ json = Request.to_json_exn req in
let person = Person.t_of_yojson json in
Logs.info (fun m -> m "Received person: %s" person.Person.name);
Response.of_json (`Assoc [ "message", `String "Person saved" ])
;;
let streaming_handler req =
let length = Body.length req.Request.body in
let content = Body.to_stream req.Request.body in
let body = Lwt_stream.map String.uppercase_ascii content in
Response.make ~body:(Body.of_stream ?length body) () |> Lwt.return
;;
let print_param_handler req =
Printf.sprintf "Hello, %s\n" (Router.param req "name")
|> Response.of_plain_text
|> Lwt.return
;;
let _ =
App.empty
|> App.post "/hello/stream" streaming_handler
|> App.get "/hello/:name" print_param_handler
|> App.get "/person/:name/:age" print_person_handler
|> App.patch "/person" update_person_handler
|> App.run_command
;;
compile and run with:
$ dune exec examples/hello_world.exe &
then make a request to be greeted with a JSON response
$ curl http://localhost:3000/person/john_doe/42
{"name":"john_doe","age":42}
The two fundamental building blocks of opium are:
Request.t -> Response.t Lwt.tRock.Handler.t -> Rock.Handler.tAlmost all of opium's functionality is assembled through various middleware. For example: debugging, routing, serving static files, etc. Creating middleware is usually the most natural way to extend an opium app.
Here's how you'd create a simple middleware turning away everyone's favourite browser.
open Opium
module Reject_user_agent = struct
let is_ua_msie =
let re = Re.compile (Re.str "MSIE") in
Re.execp re
;;
let m =
let filter handler req =
match Request.header "user-agent" req with
| Some ua when is_ua_msie ua ->
Response.of_plain_text ~status:`Bad_request "Please upgrade your browser"
|> Lwt.return
| _ -> handler req
in
Rock.Middleware.create ~filter ~name:"Reject User-Agent"
;;
end
let index_handler _request = Response.of_plain_text "Hello World!" |> Lwt.return
let _ =
App.empty
|> App.get "/" index_handler
|> App.middleware Reject_user_agent.m
|> App.cmd_name "Reject UA"
|> App.run_command
;;
Compile with:
$ dune build example/simple_middleware/main.ml
Here we also use the ability of Opium to generate a cmdliner term to run your
app. Run your executable with -h to see the options that are available to you.
For example:
# run in debug mode on port 9000
$ dune exec dune build example/simple_middleware/main.exe -- -p 9000 -d
(top 30 of 34)
OCaml
96.5%
Makefile
1.2%
Sinatra like web toolkit for OCaml
782
stars
840
commits
OCaml
primary language
Jan 6, 2026
updated
Since version 0.19.0, Opium uses httpaf. The last version that used Cohttp can be found at https://github.com/rgrinberg/opium/tree/0.18.0
Sinatra like web toolkit for OCaml based on httpaf & lwt
Opium should be very small and easily learnable. A programmer should be instantly productive when starting out.
Opium should be extensible using independently developed plugins. This is a
Rack inspired mechanism borrowed from Ruby. The middleware mechanism in
Opium is called Rock.
The latest stable version is available on opam
$ opam install opium
$ opam pin add rock.~dev https://github.com/rgrinberg/opium.git
$ opam pin add opium.~dev https://github.com/rgrinberg/opium.git
For the API documentation:
odig:
odig doc opium.The following tutorials walk through various usecases of Opium:
For examples of idiomatic usage, see the ./example directory and the simple examples below.
Assuming the necessary dependencies are installed, $ dune build @example will
compile all examples. The binaries are located in _build/default/example/.
You can execute these binaries directly, though in the examples below we use
dune exec to run them.
Here's a simple hello world example to get your feet wet:
$ cat hello_world.ml
open Opium
module Person = struct
type t =
{ name : string
; age : int
}
let yojson_of_t t = `Assoc [ "name", `String t.name; "age", `Int t.age ]
let t_of_yojson yojson =
match yojson with
| `Assoc [ ("name", `String name); ("age", `Int age) ] -> { name; age }
| _ -> failwith "invalid person json"
;;
end
let print_person_handler req =
let name = Router.param req "name" in
let age = Router.param req "age" |> int_of_string in
let person = { Person.name; age } |> Person.yojson_of_t in
Lwt.return (Response.of_json person)
;;
let update_person_handler req =
let open Lwt.Syntax in
let+ json = Request.to_json_exn req in
let person = Person.t_of_yojson json in
Logs.info (fun m -> m "Received person: %s" person.Person.name);
Response.of_json (`Assoc [ "message", `String "Person saved" ])
;;
let streaming_handler req =
let length = Body.length req.Request.body in
let content = Body.to_stream req.Request.body in
let body = Lwt_stream.map String.uppercase_ascii content in
Response.make ~body:(Body.of_stream ?length body) () |> Lwt.return
;;
let print_param_handler req =
Printf.sprintf "Hello, %s\n" (Router.param req "name")
|> Response.of_plain_text
|> Lwt.return
;;
let _ =
App.empty
|> App.post "/hello/stream" streaming_handler
|> App.get "/hello/:name" print_param_handler
|> App.get "/person/:name/:age" print_person_handler
|> App.patch "/person" update_person_handler
|> App.run_command
;;
compile and run with:
$ dune exec examples/hello_world.exe &
then make a request to be greeted with a JSON response
$ curl http://localhost:3000/person/john_doe/42
{"name":"john_doe","age":42}
The two fundamental building blocks of opium are:
Request.t -> Response.t Lwt.tRock.Handler.t -> Rock.Handler.tAlmost all of opium's functionality is assembled through various middleware. For example: debugging, routing, serving static files, etc. Creating middleware is usually the most natural way to extend an opium app.
Here's how you'd create a simple middleware turning away everyone's favourite browser.
open Opium
module Reject_user_agent = struct
let is_ua_msie =
let re = Re.compile (Re.str "MSIE") in
Re.execp re
;;
let m =
let filter handler req =
match Request.header "user-agent" req with
| Some ua when is_ua_msie ua ->
Response.of_plain_text ~status:`Bad_request "Please upgrade your browser"
|> Lwt.return
| _ -> handler req
in
Rock.Middleware.create ~filter ~name:"Reject User-Agent"
;;
end
let index_handler _request = Response.of_plain_text "Hello World!" |> Lwt.return
let _ =
App.empty
|> App.get "/" index_handler
|> App.middleware Reject_user_agent.m
|> App.cmd_name "Reject UA"
|> App.run_command
;;
Compile with:
$ dune build example/simple_middleware/main.ml
Here we also use the ability of Opium to generate a cmdliner term to run your
app. Run your executable with -h to see the options that are available to you.
For example:
# run in debug mode on port 9000
$ dune exec dune build example/simple_middleware/main.exe -- -p 9000 -d
(top 30 of 34)
OCaml
96.5%
Makefile
1.2%