dream/example/h-sql/README.md
2021-04-07 13:23:41 +03:00

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!

Comments


Several things are going on in this example. It...

  • sets up the boilerplate for two SQL queries, list_comments and add_comment with 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 AUTOINCREMENT by SERIAL 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



Next steps:

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

Up to the tutorial index