diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 59e2066b..f9d65035 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: with: ocaml-version: ${{matrix.ocaml}} + - run: opam depext --yes conf-postgresql - run: opam depext --yes conf-libev - run: opam install --yes --deps-only --with-test . - run: opam exec -- dune runtest diff --git a/dream.opam b/dream.opam index 897ea761..903f8457 100644 --- a/dream.opam +++ b/dream.opam @@ -96,6 +96,7 @@ depends: [ # Testing, development. "alcotest" {with-test} "bisect_ppx" {with-test & >= "2.5.0"} # --instrument-with. + "caqti-driver-postgresql" {with-test} "caqti-driver-sqlite3" {with-test} "crunch" {with-test} "lambdasoup" {with-test} diff --git a/example/README.md b/example/README.md index 289c216d..2c25e3af 100644 --- a/example/README.md +++ b/example/README.md @@ -131,6 +131,9 @@ if something is missing!  —  benchmarks sending WebSocket messages quickly. - [**`w-multipart-dump`**](w-multipart-dump#files)  —  echoes `multipart/form-data` bodies for debugging. +- [**`w-docker-postgres`**](w-docker-postgres#files) +  —  illustrates how to run Dream and + PostgreSQL in Docker containers using Docker Compose. - [**`z-playground`**](z-playground#files)  —  source code of the Dream playground. @@ -187,7 +190,6 @@ Techniques: - `w-graphql-sql` - `w-graphql-mutation` - `w-https-redirect` -- `w-postgres-docker` - `w-sql-stream` - `w-websocket-stream` diff --git a/example/w-docker-postgres/.dockerignore b/example/w-docker-postgres/.dockerignore new file mode 100644 index 00000000..ee7ba21f --- /dev/null +++ b/example/w-docker-postgres/.dockerignore @@ -0,0 +1,13 @@ +# esy build environment +_esy/ + +# esy dependencies +node_modules/ + +# git +.git/ +.gitignore + +# Development +Dockerfile +docker-compose.yml diff --git a/example/w-docker-postgres/Dockerfile b/example/w-docker-postgres/Dockerfile new file mode 100644 index 00000000..f58f8c7b --- /dev/null +++ b/example/w-docker-postgres/Dockerfile @@ -0,0 +1,24 @@ +FROM ocaml/opam:alpine as build + +# Install system dependencies +RUN sudo apk add --update libev-dev openssl-dev libpq postgresql-dev + +WORKDIR /home/opam + +# Install dependencies +ADD app.opam app.opam +RUN opam install . --deps-only + +# Build project +ADD . . +RUN opam exec -- dune build + + + +FROM alpine:3.12 as run + +RUN apk add --update libev libpq postgresql-dev + +COPY --from=build /home/opam/_build/default/app.exe /bin/app + +ENTRYPOINT /bin/app diff --git a/example/w-docker-postgres/README.md b/example/w-docker-postgres/README.md new file mode 100644 index 00000000..b0b45ef8 --- /dev/null +++ b/example/w-docker-postgres/README.md @@ -0,0 +1,83 @@ +# `w-docker-postgres` + +
+ +This example illustrates how to use +[Docker](https://en.wikipedia.org/wiki/Docker_(software)) and +[docker-compose](https://docs.docker.com/compose/) to manage a simple +web server with a Postgres database. + +Running a database under docker can simplify the process of setting up +a development environment. This is especially true if your application +uses several databases or services and you want a convenient way to +manage them all with a single tool. + +The example app allows users to see a list of existing comments and +post new comments. Take a look at this +[example](https://github.com/aantron/dream/tree/master/example/h-sql#files) +for more background information. + +## Getting Set Up + +Start your docker containers by running `docker-compose up -d`. It +will take some time to build the `web` container; subsequent rebuilds +are faster. + +When both containers are available, you should see: +``` +Creating network "w-docker-postgres_default" with the default driver +Creating w-docker-postgres_postgres_1 ... done +Creating w-docker-postgres_web_1 ... done +``` + +At this point the PostgreSQL database is ready but doesn't have any +tables. Fix that by running: + +``` +docker-compose exec postgres psql -U dream -c "$(cat schema.sql)" +``` + +This will create the `comment` and `dream_session` tables described in +`schema.sql`. + +Finally, open your browser to +[`http://localhost:8080/`](http://localhost:8080/) to try the +application. + + +## Tips + +If you modify `app.eml.ml`, you will need to run +``` +docker-compose build && docker-compose up web +``` +to rebuild the `web` container and see your changes. + +To view the logs from the API container (rather than having them mixed +in with the logs from the database) run: +``` +docker-compose logs web -f +``` + +The database container contains a copy of the `psql` client. Running + +``` +docker-compose exec postgres psql -U dream +``` + +will allow you to start the client and run queries interactively. + +
+ +**See also:** + +- [**`h-sql`**](../h-sql#files) this example contains essentially the + same application, but uses sqlite. +- [**`z-docker-esy`**](../z-docker-esy#files) describes how to deploy + with docker-compose and esy. +- [**`z-docker-opam`**](../z-docker-opam#files) describes how to deploy + with docker-compose and opam. + +
+ +[Up to the example index](../#examples) diff --git a/example/w-docker-postgres/app.eml.ml b/example/w-docker-postgres/app.eml.ml new file mode 100644 index 00000000..7f8fb79e --- /dev/null +++ b/example/w-docker-postgres/app.eml.ml @@ -0,0 +1,58 @@ +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 = + + +% comments |> List.iter (fun (_id, comment) -> +

<%s comment %>

<% ); %> + + <%s! Dream.form_tag ~action:"/" request %> + + + + + + + +let () = + Dream.run ~interface:"0.0.0.0" + @@ Dream.logger + @@ Dream.sql_pool "postgresql://dream@postgres/dream" + @@ Dream.sql_sessions + @@ Dream.router [ + + Dream.get "/" (fun request -> + let%lwt comments = Dream.sql request list_comments in + Dream.html (render comments request)); + + Dream.post "/" (fun request -> + match%lwt Dream.form request with + | `Ok ["text", text] -> + let%lwt () = Dream.sql request (add_comment text) in + Dream.redirect request "/" + | _ -> + Dream.empty `Bad_Request); + + ] + @@ Dream.not_found diff --git a/example/w-docker-postgres/app.opam b/example/w-docker-postgres/app.opam new file mode 100644 index 00000000..51fa6433 --- /dev/null +++ b/example/w-docker-postgres/app.opam @@ -0,0 +1,8 @@ +opam-version: "2.0" + +depends: [ + "ocaml" {>= "4.08.0"} + "dune" {>= "2.0.0"} + "dream" + "caqti-driver-postgresql" +] diff --git a/example/w-docker-postgres/docker-compose.yml b/example/w-docker-postgres/docker-compose.yml new file mode 100644 index 00000000..c3adc162 --- /dev/null +++ b/example/w-docker-postgres/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3" + +services: + postgres: + image: postgres + environment: + - POSTGRES_USER=dream + - POSTGRES_DB=dream + + web: + build: . + expose: + - 8080 + ports: + - "8080:8080" + links: + - postgres + restart: always + logging: + driver: ${LOGGING_DRIVER:-json-file} diff --git a/example/w-docker-postgres/dune b/example/w-docker-postgres/dune new file mode 100644 index 00000000..f4c2e2e0 --- /dev/null +++ b/example/w-docker-postgres/dune @@ -0,0 +1,12 @@ +(executable + (name app) + (libraries caqti-driver-postgresql dream) + (preprocess (pps lwt_ppx)) +) + +(rule + (targets app.ml) + (deps app.eml.ml) + (action (run dream_eml %{deps} --workspace %{workspace_root}))) + +(data_only_dirs _esy esy.lock) diff --git a/example/w-docker-postgres/dune-project b/example/w-docker-postgres/dune-project new file mode 100644 index 00000000..929c696e --- /dev/null +++ b/example/w-docker-postgres/dune-project @@ -0,0 +1 @@ +(lang dune 2.0) diff --git a/example/w-docker-postgres/schema.sql b/example/w-docker-postgres/schema.sql new file mode 100644 index 00000000..9947e71f --- /dev/null +++ b/example/w-docker-postgres/schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE comment ( + id SERIAL PRIMARY KEY, + text TEXT NOT NULL +); + +CREATE TABLE dream_session ( + id TEXT PRIMARY KEY, + label TEXT NOT NULL, + expires_at REAL NOT NULL, + payload TEXT NOT NULL +);