4.6 KiB
h-sql
Let's serve a list of comments with a comment form!
module type DB = Caqti_lwt.CONNECTION
module R = Caqti_request
module T = Caqti_type
let list_comments =
let query =
R.collect T.unit T.(tup2 int string)
"SELECT id, text FROM comment" in
fun (module Db : DB) ->
let%lwt comments_or_error = Db.collect_list query () in
Caqti_lwt.or_fail comments_or_error
let add_comment =
let query =
R.exec T.string
"INSERT INTO comment (text) VALUES ($1)" in
fun text (module Db : DB) ->
let%lwt unit_or_error = Db.exec query text in
Caqti_lwt.or_fail unit_or_error
let render comments request =
<html>
<body>
% comments |> List.iter (fun (_id, comment) ->
<p><%s comment %></p><% ); %>
<%s! Dream.Tag.form ~action:"/" request %>
<input name="text">
</form>
</body>
</html>
let () =
Dream.run
@@ Dream.logger
@@ Dream.sql_pool "sqlite3:db.sqlite"
@@ Dream.sql_sessions
@@ Dream.router [
Dream.get "/" (fun request ->
let%lwt comments = Dream.sql list_comments request in
Dream.respond (render comments request));
Dream.post "/" (fun request ->
match%lwt Dream.form request with
| `Ok ["text", text] ->
let%lwt () = Dream.sql (add_comment text) request in
let%lwt comments = Dream.sql list_comments request in
Dream.respond (render comments request)
| _ ->
Dream.empty `Bad_Request);
]
@@ Dream.not_found
$ dune exec --root . ./sql.exe
Try visiting http://localhost:8080 and leaving some comments!
Several things are going on in this example. It...
- sets up the boilerplate for two SQL queries,
list_commentsandadd_commentwith Caqti; - defines a template,
render, for our app's main page; - sets up a
pool of SQL connections to
db.sqlite; and - sets up two routes, one for displaying the comment list, and one for
receiving new comments from our CSRF-safe form (example
d-form).
We also take the opportunity to try out
Dream.sql_sessions, which
stores session data persistently in db.sqlite! See example
b-session for an introduction to session management.
db.sqlite was initialized with this schema, using the sqlite3 command:
CREATE TABLE comment (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text NOT NULL);
CREATE TABLE dream_session (
key TEXT NOT NULL PRIMARY KEY,
id TEXT NOT NULL,
expires_at REAL NOT NULL,
payload TEXT NOT NULL
);
We also had to make an addition to our Dune file:
(executable
(name sql)
(libraries caqti-driver-sqlite3 dream)
(preprocess (pps lwt_ppx)))
(rule
(targets sql.ml)
(deps sql.eml.ml)
(action (run dream_eml %{deps} --workspace %{workspace_root})))
SQLite is good for small-to-medium sites and examples. For a larger site, microservices, or other needs, you can switch, for example, to PostgreSQL by...
- running PostgreSQL, typically in a Docker container;
- changing the connection URI to
postgres://user:password@host:port; - using
caqti-driver-postgres; - replacing
INTEGER PRIMARY KEY AUTOINCREMENTbySERIAL PRIMARY KEY.
A good program for examining the database locally is Beekeeper Studio. Dream might also integrate an optional hosted database UI in the future, and you could choose to serve it at some route.
See
Caqti_connect_sig.S.CONNECTIONfor Caqti's statement runners. These are the fields of the moduleDbin the example.Caqti_requestsets up prepared statements.Caqti_typeis used to specify the types of statement arguments and results.
Next steps:
