Skip to content

Commit efe353d

Browse files
committed
Add 'Database-Driven Todos' section
1 parent 4245914 commit efe353d

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

index.adoc

+122
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Usage:
7979
Options:
8080
-c, --cider Start an NREPL server with CIDER middleware
8181
--init Create a blank duct.edn config file
82+
-k, --keys KEYS Limit --main to start only the supplied keys
8283
-p, --profiles PROFILES A concatenated list of profile keys
8384
-n, --nrepl Start an NREPL server
8485
-m, --main Start the application
@@ -869,9 +870,13 @@ command-line argument or environment variable to override this default.
869870
----
870871
user=> (reset)
871872
:reloading ()
873+
:user/added (db sql)
872874
:resumed
873875
----
874876

877+
NOTE: The `:user/added` message informs you about convenience functions
878+
that have been added to the REPL environment in the user namespace.
879+
875880
The SQL module adds a database connection pool under the key
876881
`:duct.database.sql/hikaricp`, which derives from the more general
877882
`:duct.database/sql` key. We can use this connection pool as a
@@ -983,3 +988,120 @@ This will run any component with a key that derives from
983988
NOTE: `:duct/logger` is often defined as an optional dependency, via a
984989
*refset*. Without explicitly specifying this as one of the keys, the
985990
migrator will run without logging.
991+
992+
=== Database-Driven Todos
993+
994+
Now that we have a database table and a web server, it's time to put the
995+
two together. The database we pass to the index function can be used to
996+
populate an unordered list. We'll change the index function accordingly.
997+
998+
.src/todo/routes.clj
999+
[,clojure]
1000+
----
1001+
(ns todo.routes
1002+
(:require [next.jdbc :as jdbc]))
1003+
1004+
(def list-todos "SELECT * FROM todo")
1005+
1006+
(defn index [{:keys [db]}]
1007+
(fn [_request]
1008+
[:html {:lang "en"}
1009+
[:head [:title "Todo"]]
1010+
[:body
1011+
[:ul (for [rs (jdbc/execute! db [list-todos])]
1012+
[:li (:todo/description rs)])]]]))
1013+
----
1014+
1015+
TIP: It's often a good idea to factor out each SQL string into its own
1016+
var. This allows them to be treated almost like function calls when
1017+
combined with `execute!`.
1018+
1019+
We can reset via the REPL and add some test data with the `sql`
1020+
convenience function.
1021+
1022+
[,shell]
1023+
----
1024+
user=> (reset)
1025+
:reloading (todo.routes)
1026+
:resumed
1027+
user=> (sql "INSERT INTO todo (description) VALUES ('Test One')")
1028+
[#:next.jdbc{:update-count 1}]
1029+
user=> (sql "INSERT INTO todo (description) VALUES ('Test Two')")
1030+
[#:next.jdbc{:update-count 1}]
1031+
----
1032+
1033+
If you visit http://localhost:3000/ you'll be able to see the todo items
1034+
that were added to the database table.
1035+
1036+
The next step is to allow for new todo items to be added through the web
1037+
interface. This is a little more involved, as we'll need a HTML form and
1038+
a route to respond to the form's POST.
1039+
1040+
First, we add a new handler, `new-todo`, to the configuration to handle
1041+
the POST.
1042+
1043+
.duct.edn
1044+
[,clojure]
1045+
----
1046+
{:vars {jdbc-url {:default "jdbc:sqlite:todo.db"}}
1047+
:system
1048+
{:duct.module/logging {}
1049+
:duct.module/sql {}
1050+
:duct.module/web
1051+
{:features #{:site}
1052+
:handler-opts {:db #ig/ref :duct.database/sql}
1053+
:routes [["/" {:get :todo.routes/index
1054+
:post :todo.routes/new-todo}]]}}}
1055+
----
1056+
1057+
Then we need incorporate the POST handler and the form into the
1058+
codebase.
1059+
1060+
.src/todo/routes.clj
1061+
[,clojure]
1062+
----
1063+
(ns todo.routes
1064+
(:require [next.jdbc :as jdbc]
1065+
[ring.middleware.anti-forgery :as af]))
1066+
1067+
(def list-todos "SELECT * FROM todo")
1068+
(def insert-todo "INSERT INTO todo (description) VALUES (?)")
1069+
1070+
(defn- create-todo-form []
1071+
[:form {:action "/" :method "post"}
1072+
[:input {:type "hidden"
1073+
:name "__anti-forgery-token"
1074+
:value af/*anti-forgery-token*}]
1075+
[:input {:type "text", :name "description"}]
1076+
[:input {:type "submit", :value "Create"}]])
1077+
1078+
(defn index [{:keys [db]}]
1079+
(fn [_request]
1080+
[:html {:lang "en"}
1081+
[:head [:title "Todo"]]
1082+
[:body
1083+
[:ul
1084+
(for [rs (jdbc/execute! db [list-todos])]
1085+
[:li (:todo/description rs)])
1086+
[:li (create-todo-form)]]]]))
1087+
1088+
(defn new-todo [{:keys [db]}]
1089+
(fn [{{:keys [description]} :params}]
1090+
(jdbc/execute! db [insert-todo description])
1091+
{:status 303, :headers {"Location" "/"}}))
1092+
----
1093+
1094+
There are two new additions here. The `create-todo-form` function
1095+
creates a form for making new todo list items. You may notice that it
1096+
includes a hidden field for setting an anti-forgery token. This prevents
1097+
a type of attack known as a
1098+
https://en.wikipedia.org/wiki/Cross-site_request_forgery[Cross-site
1099+
request forgery].
1100+
1101+
The second addition is the `new-todo` function. This inserts a new row
1102+
into the todo table, then returns a "`303 See Other`" response that will
1103+
redirect the browser back to the index page.
1104+
1105+
If we reset via the REPL and check http://localhost:3000/, you should
1106+
see a text input box at the bottom of the todo list, allowing more todo
1107+
items to be added.

0 commit comments

Comments
 (0)