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 =
+
+
<%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 +);