building crud applications in clojure - meetupfiles.meetup.com/10978482/building crud applications...

Post on 20-May-2020

30 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Building CRUD applications in Clojure

https://github.com/danielytics/diy-crud/

What do we need?

• Request Handler

• Routing

• Database Access

• Templating

DIY CRUD

• Request Handler

• Routing

• Database Access

• Templating

Ring

Compojure, bidi, pedestal, silk, ...

Yesql, korma, lots of others for noSQL databases

Enlive, enliven, selmer, hiccup, clostache, ...

Lots of options!

DIY CRUD

• Request Handler

• Routing

• Database Access

• Templating

Ring

Bidi

Yesql

Erinite/template

DIY CRUD

• Request Handler

• Routing

• Database Access

• Templating

Ring

Bidi

Yesql

Erinite/template

Why ring?

• Defacto standard

DIY CRUD

• Request Handler

• Routing

• Database Access

• Templating

Ring

Bidi

Yesql

Erinite/template

Why bidi?

• Routes are data

• Reversible (URL -> route, route -> URL)

• Clojure and Clojurescript

DIY CRUD

• Request Handler

• Routing

• Database Access

• Templating

Ring

Bidi

Yesql

Erinite/template

Why Yesql?

• Works with JDBC

• Write SQL as SQL

DIY CRUD

• Request Handler

• Routing

• Database Access

• Templating

Ring

Bidi

Yesql

Erinite/template

Why Erinite/template?

• Because I wrote it ;-)

• USP: template manipulation through data

• Will go into more detail later in this talk

DIY CRUD setup a project

$ lein new crud-talk; cd crud-talk; vim project.clj

:dependencies [[org.clojure/clojure “1.7.0”]

[ring “1.4.0-RC1”]

[bidi “1.20.0”]

[yesql "0.4.0“ :exclusions [instaparse]]

[instaparse “1.4.1”] ; For clj 1.7 support

[org.xerial/sqlite-jdbc "3.7.2"]

[hiccup "1.0.5"]

[erinite/template “0.2.0”]]

:plugins [[lein-ring "0.9.6"]]

:ring {:handler crud-talk.core/handler}

DIY CRUD create handler

$ vim src/crud-talk/core.clj

(ns crud-talk.core

(:require

[bidi.ring :as bidi-ring]

[yesql.core :refer [defqueries]]

[erinite.template.core :as t]

[hiccup.core :refer [html]]))

;; Other code goes here...

(def handler (bidi-ring/make-handler routes))

DIY CRUD create our routes

(def routes

["/" {"items"

{{:request-method :get} read-items

{:request-method :post} create-item}

["item/" :item-id]

{{:request-method :get} read-item

{:request-method :put} update-item

{:request-method :post} delete-item

{:request-method :delete} delete-item}}])

DIY CRUD create our routes

(def routes

["/" {"items"

{{:request-method :get} read-items

{:request-method :post} create-item}

["item/" :item-id]

{{:request-method :get} read-item

{:request-method :put} update-item

{:request-method :post} delete-item

{:request-method :delete} delete-item}}])

So we can test in browser without using JS

DIY CRUD read a list of items

-- name: get-all-items

-- Read a summary list of all items in database.

SELECT id, name, quantity

FROM items

DIY CRUD create database queries

(def db-spec {:classname "org.sqlite.JDBC"

:subprotocol "sqlite"

:subname "db.sqlite"})

(defqueries “queries.sql”)

DIY CRUD create database queries

Make sure you have a database setup:

$ sqlite db.sqlite

sqlite> create table items (id int, name text, description text, qunantity int);

sqlite> insert into items values (1, ‘Test item’, ‘A test item. The first one’, 5);

sqlite> insert into items values (2, ‘Another item’, ‘A test item. The second one’, 26);

sqlite> insert into items values (3, ‘Item’, ‘The third item’, 3);

DIY CRUD create template

(def item-list-template

[:div

[:h1 "Item List"]

[:table.items

[:tr

[:td.id] [:td.name] [:td.quantity]

[:td [:form {:method “post”} [:button {:type "submit"} "delete"]]]]]

[:hr] [:div "New item"]

[:form#new {:action "/items“ :method "post"}

[:input.name {:type "text"}]

[:input.description {:type "text"}]

[:input.quantity {:type "text"}]

[:button {:type "submit"} "add"]]])

DIY CRUD transform template

(def item-list-transformations

{[:.items] [:clone-for :items]

[:.items :.id] [:content :id]

[:.items :.name] [:content :name]

[:.items :.quantity] [:content :quantity]

[:.items :form] [:set-attr :action :url]})

DIY CRUD compile template

(def item-list

(t/compile-template

item-list-template

item-list-transformation))

DIY CRUD item list handler

(defn read-items [request]

(let [items (map

#(assoc % :url (str "/item/" (:id %)))

(get-all-items db-spec))]

{:status 200

:body (html (item-list {:items items}))}))

DIY CRUD item list handler

(defn read-items [request]

(let [items (map

#(assoc % :url (str "/item/" (:id %)))

(get-all-items db-spec))]

{:status 200

:body (html (item-list {:items items}))}))

DIY CRUD the data

({:id 1

:name “Test item”

:quantity 5}

{:id 2

:name “Another item”

:quantity 26}

{:id 3

:name “Item”

:quantity 3})

DIY CRUD item list handler

(defn read-items [request]

(let [items (map

#(assoc % :url (str "/item/" (:id %)))

(get-all-items db-spec))]

{:status 200

:body (html (item-list {:items items}))}))

DIY CRUD the data

({:id 1

:name “Test item”

:url “/item/1”

:quantity 5}

{:id 2

:name “Another item”

:url “/item/2”

:quantity 26}

{:id 3

:name “Item”

:url “/item/3”

:quantity 3})

DIY CRUD item list handler

(defn read-items [request]

(let [items (map

#(assoc % :url (str "/item/" (:id %)))

(get-all-items db-spec))]

{:status 200

:body (html (item-list {:items items}))}))

DIY CRUD the data

{:items ({:id 1

:name “Test item”

:url “/item/1”

:quantity 5}

{:id 2

:name “Another item”

:url “/item/2”

:quantity 26}

{:id 3

:name “Item”

:url “/item/3”

:quantity 3})

DIY CRUD item list handler

(defn read-items [request]

(let [items (map

#(assoc % :url (str "/item/" (:id %)))

(get-all-items db-spec))]

{:status 200

:body (html (item-list {:items items}))}))

DIY CRUD the data

[:div {}

[:h1 {} "Item List"]

[:table {:class “items”}

[:tr {}

[:td {:class “id”} 1]

[:td {:class “name”} “Test Item”]

[:td {:class “quantity”} 5]

[:td {}

[:form {:action “/item/1” :method “post”}

[:button {:type "submit“} "delete"]]]]]

...

DIY CRUD item list handler

(defn read-items [request]

(let [items (map

#(assoc % :url (str "/item/" (:id %)))

(get-all-items db-spec))]

{:status 200

:body (html (item-list {:items items}))}))

DIY CRUD the data

<div>

<h1>Item List</h1>

<table class=“items”>

<tr>

<td class=“id”> 1</td>

<td class=“name”>Test Item</td>

<td class=“quantity”>5</td>

<td>

<form action=“/item/1” method=“post”>

<button type="submit“>delete</button>

</form>

</td>

</tr>

...

DIY CRUD item list handler

(defn read-items [request]

(let [items (map

#(assoc % :url (str "/item/" (:id %)))

(get-all-items db-spec))]

{:status 200

:body (html (item-list {:items items}))}))

DIY CRUD test run!

$ lein ring server

Now open localhost:3001/items

DIY CRUD Erinite/template crash course

(def item-list-template

[:div

[:h1 "Item List"]

[:table.items

[:tr

[:td.id] [:td.name] [:td.quantity]

[:td [:form {:method “post”} [:button {:type "submit“} "delete"]]]]]

[:hr] [:div "New item"]

[:form#new {:action "/items“ :method "post"}

[:input.name {:type "text"}]

[:input.description {:type "text"}]

[:input.quantity {:type "text"}]

[:button {:type "submit"} "add"]]])

DIY CRUD Erinite/template crash course

(def item-list-template

[:div

[:h1 "Item List"]

[:table.items

[:tr

[:td.id] [:td.name] [:td.quantity]

[:td [:form {:method “post”} [:button {:type "submit“} "delete"]]]]]

[:hr] [:div "New item"]

[:form#new {:action "/items“ :method "post"}

[:input.name {:type "text"}]

[:input.description {:type "text"}]

[:input.quantity {:type "text"}]

[:button {:type "submit"} "add"]]])

[:.items] [:clone-for :items]

DIY CRUD Erinite/template crash course

(def item-list-template

[:div

[:h1 "Item List"]

[:table.items

[:tr

[:td.id] [:td.name] [:td.quantity]

[:td [:form {:method “post”} [:button {:type "submit“} "delete"]]]]]

[:hr] [:div "New item"]

[:form#new {:action "/items“ :method "post"}

[:input.name {:type "text"}]

[:input.description {:type "text"}]

[:input.quantity {:type "text"}]

[:button {:type "submit"} "add"]]])

[:.items] [:clone-for :items]

DIY CRUD Erinite/template crash course

(def item-list-template

[:div

[:h1 "Item List"]

[:table.items

[:tr

[:td.id] [:td.name] [:td.quantity]

[:td [:form {:method “post”} [:button {:type "submit"} "delete"]]]]]

[:hr] [:div "New item"]

[:form#new {:action "/items“ :method "post"}

[:input.name {:type "text"}]

[:input.description {:type "text"}]

[:input.quantity {:type "text"}]

[:button {:type "submit"} "add"]]])

[:.items] [:clone-for :items]

{:items ({:id 1

:name “Test item”

:url “/item/1”

:quantity 5}

{:id 2

:name “Another item”

:url “/item/2”

:quantity 26}

{:id 3

:name “Item”

:url “/item/3”

:quantity 3})

DIY CRUD Erinite/template crash course

(def item-list-template

[:div

[:h1 "Item List"]

[:table.items

[:tr

[:td.id] [:td.name] [:td.quantity]

[:td [:form {:method “post”} [:button {:type "submit“} "delete"]]]]]

[:hr] [:div "New item"]

[:form#new {:action "/items“ :method "post"}

[:input.name {:type "text"}]

[:input.description {:type "text"}]

[:input.quantity {:type "text"}]

[:button {:type "submit"} "add"]]])

[:.items :.id] [:content :id]

DIY CRUD Erinite/template crash course

(def item-list-template

[:div

[:h1 "Item List"]

[:table.items

[:tr

[:td.id] [:td.name] [:td.quantity]

[:td [:form {:method “post”} [:button {:type "submit“} "delete"]]]]]

[:hr] [:div "New item"]

[:form#new {:action "/items“ :method "post"}

[:input.name {:type "text"}]

[:input.description {:type "text"}]

[:input.quantity {:type "text"}]

[:button {:type "submit"} "add"]]])

[:.items :.id] [:content :id]

{:id 1

:name “Test item”

:url “/item/1”

:quantity 5}

DIY CRUD detour: erinite/template-stylesheets

Detour Time!

Erinite/template comes with a companion library: erinite/template-stylesheets

DIY CRUD detour: erinite/template-stylesheets

Detour Time!

Erinite/template comes with a companion library: erinite/template-stylesheets

{[:.items] [:clone-for :items][:.items :.id] [:content :id][:.items :.name] [:content :name][:.items :.quantity] [:content :quantity][:.items :form] [:set-attr :action :url]}

DIY CRUD detour: erinite/template-stylesheets

Detour Time!

Erinite/template comes with a companion library: erinite/template-stylesheets

{[:.items] [:clone-for :items][:.items :.id] [:content :id][:.items :.name] [:content :name][:.items :.quantity] [:content :quantity][:.items :form] [:set-attr :action :url]}

DIY CRUD detour: erinite/template-stylesheets

.items {clone-for: items;

}.items .id {

content: id;}.items .name {

content: name;}.items .quantity {

content: quantity;}.items form {

set-attr: action url;}

DIY CRUD detour: erinite/template-stylesheets

.items {clone-for: items;

}.items .id {

content: id;}.items .name {

content: name;}.items .quantity {

content: quantity;}.items form {

set-attr: action url;}

Now your designers can update yourtemplate transformation selectors!

DIY CRUD deleting items

-- name: delete-item!

-- Delete a specific item from the database.

DELETE FROM items

WHERE id = :id

DIY CRUD deleting items

(defn delete-item [request]

(let [item-id (get-in request

[:route-params :item-id])]

(delete-item! db-spec item-id)

{:status 302

:headers {"Location" "/items"}}))

DIY CRUD deleting items

(defn delete-item [request]

(let [item-id (get-in request

[:route-params :item-id])]

(delete-item! db-spec item-id)

{:status 302

:headers {"Location" "/items"}}))

DIY CRUD create our routes

(def routes

["/" {"items"

{{:request-method :get} read-items

{:request-method :post} create-item}

["item/" :item-id]

{{:request-method :get} read-item

{:request-method :put} update-item

{:request-method :post} delete-item

{:request-method :delete} delete-item}}])

DIY CRUD deleting items

(defn delete-item [request]

(let [item-id (get-in request

[:route-params :item-id])]

(delete-item! db-spec item-id)

{:status 302

:headers {"Location" "/items"}}))

DIY CRUD deleting items

(defn delete-item [request]

(let [item-id (get-in request

[:route-params :item-id])]

(delete-item! db-spec item-id)

{:status 302

:headers {"Location" "/items"}}))

DIY CRUD

...and so on for create and update.

Where to next?

Liberator

Define resources as conditions and actions in a state machine.

Very flexible

Makes it easy to be RFC compliant

But is lower level than I’d like

Where to next?

Yada

JUXT’s new Liberator competitor

Simpler and higher level than Liberator

Built-in swagger support

Where to next?

Erinite/crud

There is no good out-of-the-box CRUD library than I know of. So I plan on making one.

https://github.com/Erinite/crud

Where to next?

Erinite/crud

Doesn’t exist yet, so I’m taking feature requests

Where to next?

Erinite/crud

The plan so far:

• Support any database (by implementing a protocol)

• Data-driven handlers (you define your resources as data, library does the rest)

• Just a handler, so you can mount it at any route you want

Questions?

top related