Anton Bachin c5514d72e3 Use esy
2021-04-21 10:49:15 +03:00
..
2021-04-13 13:00:02 +03:00
2021-04-07 13:23:41 +03:00
2021-04-07 13:23:41 +03:00
2021-04-21 10:49:15 +03:00
2021-04-21 10:49:15 +03:00
2021-04-19 09:04:44 +03:00

h-sql


Let's serve a list of comments with a comment form! First, we define a couple of prepared statements using Caqti, a library for talking to SQL databases:

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

...then, a template for our CSRF-safe form, like in example d-form:

let render comments request =
  <html>
    <body>

%     comments |> List.iter (fun (_id, comment) ->
        <p><%s comment %></p><% ); %>

      <%s! Dream.form_tag ~action:"/" request %>
        <input name="text">
      </form>

    </body>
  </html>

...and finally, the Web app itself, which connects to db.sqlite:

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.html (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
        Dream.redirect "/"
      | _ ->
        Dream.empty `Bad_Request);

  ]
  @@ Dream.not_found
$ npm install esy && npx esy
$ npx esy start

Try visiting http://localhost:8080 and leaving some comments!

Comments


We 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. Both the comments and the sessions survive server restarts.


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 (
  id TEXT PRIMARY KEY,
  label 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})))

...and to esy.json:

"dependencies": {
  "@opam/caqti-driver-sqlite3": "*"
}

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 AUTOINCREMENT by SERIAL PRIMARY KEY.

A good program for examining databases 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



Next steps:

  • i-graphql handles GraphQL queries and serves GraphiQL.
  • j-stream streams response bodies to clients.

Up to the tutorial index