Advertisement

Clojure web App with interaction with MySQL

Introduction

Web development in Clojure is flexible and framework-less in the sense that no monolithic opinionated framework like Rails or Phoenix is used. Recently we saw in an article “Phoenix,Ecto and MySQL Web CRUD App” the ease of use brought in by the opinionated Phoenix. In fact the initial versions of “mix” , the Elixir build tool , was the creation of Leiningen people. As we saw earlier, mix was used to create and set up Elixir projects; similarly  “lein” is  used to create and set up Clojure projects. As Clojure community values flexibility there are a number of “lein” templates for different types of applications making use of different libraries. You can use one of them depending on your need. The application created with any template can be deployed out of box and it only lacks application logic. Inspired by, Rails, Phoenix has mix “tasks” for generating various artefacts/application logic. But Clojure community has chosen not to provide for such tasks. You can listen to the talk by Rich Hickey, creator of Clojure, “Simple made Easy”.

  For creating our sample, we will use ‘compojure-app’ template we used in our earlier article. Luminus is another lein template preferred by most for creating web applications in Clojure.  We will create only simple web application that stores and retrieves data from MySQL using clojure.java.jdbc we saw earlier. It will not require the functionalities provided by the many libraries used by Luminus.

Ring, Ring-server,Compojure and Hiccup  libraries:

Our template uses the above libraries 1)Ring library is the current de facto standard base from which web applications in Clojure are written. It is actually a wrapper around Java Servlet. So a web application built on top of Ring can be deployed on any Servlet container. 2) To enable stand-alone deployment Ring-Server, another library, which comes with embedded Jetty server is used. In place of Ring-server, other servers can be used. Luminus template uses only Ring and does not use Ring Server and provides for the use of Immutant or http-kit server as you choose. 3) Compojure is a routing library built on top of Ring 4) Hiccup is the library used to generate forms for user-interaction and generate the layout common to all pages. 

 b) their functionalities:  

1)Ring  abstracts the details of HTTP into a concise and modular API that can be used to build a large spectrum of applications..The Ring handlers are functions that process the incoming requests and generate the responses.  Ring takes care of converting the HTTP servlet request into a Clojure map and passing the same into a handler. It also converts the response map returned by a handler into the corresponding HTTP response object. The Ring stack consists of a chain of middleware functions. Each function accepts the request map, modifies it in some way, then passes it on to the next function in the chain. They provide additional functionalities like session, cookies etc when a request is handled. The middleware functions have to be wrapped in order of dependency.

2) The Ring-server library comes with a Jetty adapter, which sits between a Jetty Servlet container and the rest of the application stack

3) Compojure, as mentioned above,  is a routing library built on top of Ring. It provides a way to associate handler functions with a URL and an HTTP method.

4) Hiccup simply uses Clojure data structures to define the mark-up that generates the corresponding HTML from it.

Sample
 
lein new compojure-app  lrn_comp

The template will generate a running web application. You can run it using
lein ring servers  

You can see some messages including

“lrn_comp is starting”
.......
“Started server on port 3000”

Project Directory

For the generated files and folders have a look at figure- below:

  In the above figure you don’t see a MVC application structure. Compojure does not enforce any strict separation between the view and the controller. Instead, handlers, which combine the functionalities of both, are created for each application route.

Files and folders  Apart from the configuration file ‘project.clj’ available in the root namespace or folder, the other major components  are as follows:

• handler — This namespace is responsible for handling requests and
responses. It contains only application handler specified in the configuration file. The app handler specifies all the Compojure routes it can handle.The generated file /src/lrn_comp/handler.clj can be used as it is. For handlers that actually process our “urls”, we have to go the  handlers in the routes namespace.

• routes — The defroutes macro is used to associated a route with a handler. The routes namespace contains the core of our application, such as the logic to render pages and handle client requests. The generated file   “/src/lrn_comp/routes/home.clj” has to be customized for the above purpose.

• model — This namespace is reserved for the data model of the application and the persistence layer.

• views — This namespace contains common logic for generating the application layout. The file  /views/layout.clj generated  for the purpose can be used as it is.

Handler Though we need not touch application handler in “handler.clj”, we can have a look at it.  The handler is the entry point for the application. In the configuration file “project.clj” , the application handler is specified as under: 

:ring {:handler lrn_comp.handler/app
......e
}
The “app”  handler is defined in handler.clj as under:
(def app
  (-> (routes home-routes app-routes)
      (handler/site)
      (wrap-base-url)))     

It is responsible for aggregating all the routes for the application and wrapping the same with any necessary middleware. You can see that in the above handler, routes for the application app-routes and home-routes are passed. We will customize the home-routes found in   “home.clj”. The routes macro will combine all the routes into a single set from which the final handler can be created. Be aware that routes can mask other routes. Please see how the above app-routes is defined using defroutes macro.

(defroutes app-routes
(route/resources "/")
(route/not-found "Not Found"))

Since our app-routes contains (route/not-found "Not Found"), we should put it last, or the not-found route will prevent any routes defined after it from resolving.

URL specific Routes:

The url  specific routes are in lrn_comp/src/lrn_comp/routes/home.clj.They are  given below:

(defroutes home-routes
(GET "/" [] (home))
(POST "/" [name content] (save-blog name content)))

In our work-flow, the first route was bound to  / and should retrieve the data from the database and render a page displaying them alongside a form for creating  a new blog. The second route should handle the user input. If the input turns out to be valid, then the blog is stored in the database; otherwise the page is rendered with an accompanying error. Since both of these routes have related functionality—storing and displaying blogs—they are considered to be part of the same workflow.We will see more of it when we see the file home.clj.

Application Data
For our application to interact with MySQL database we need to add the following to the dependencies in “project.clj” as in our earlier article “Clojure App with interaction with MySQL”.

[org.clojure/java.jdbc    "0.7.0-alpha1"]
      [mysql/mysql-connector-java    "5.1.18"]

Under /models folder we can create the file db.clj which contains the code for interaction with the MySQL database. The code for the file is given below:

/models/db.clj
(ns    lrn_comp.models.db
(:require [clojure.java.jdbc :as sql]))
(def db { :subprotocol "mysql" :subname "//127.0.0.1:3306/blogdb" :user "root" :password "mysql"}) (defn read-data [](sql/query db ["select * from blog"] ))
(defn save-data [name, content] (sql/insert! db :blog { :name name :content content}))

The code is the same as the one given in the core.clj in the earlier article mentioned above except the following:

  1. the query function is put within the helper function “read-data”  
  2. the function insert! Is coded within the helper function save-data.

One may ask why should we do it? If you have a look at the code for the  “home” page, you can understand the same.

Home clj
The code for “routes\home.clj”  that actually contains our application logic is given below:

/routes/home.clj
(ns    lrn_comp.routes.home
;; All functions in compojure.core and views/layout.clj,hiccup.form and models.db   are required

 

  (:require [compojure.core :refer :all]
[lrn_comp.views.layout :as layout] [hiccup.form :refer :all] [lrn_comp.models.db :as db]))

      ;; this function will be used by home handler later    
(defn show-blogs [] [:ul.blogs (for [{:keys [content name ]} (db/read-data)] [:li [:blockquote content] [:p "-" [:cite name]] ])])

            (defn home [& [name content error]]
;;common() function in “hiccup.layout”  is used and it is passed a vector containing
a string to be sent in Response.

              (layout/common [:h1 "Blog-Site"] [:p "Welcome to my blog-site"] [:p error]

               ;here we call our show-blogs function ;to generate the list of existing blogs (show-blogs)

               [:hr]

               ;;here we create a form using the function in hiccup with text fields named
"name" and "content"
; ;these will be sent when the form posts to the server as keywords of the same name
(form-to [:post "/"] [:p "Name:"] (text-field "name" name)

                 [:p "Content:"] (text-area {:rows 5 :cols 40} "content" content)
                 [:br]
(submit-button "comment")))) ;; this function is invoked when the form is posted (defn save-content [name content] (cond (empty? name) (home name content "Furnish a  name") (empty? content) (home name content "Don't you have something to say?") :else (do
(db/save-data name content) ;;the function in db.clj is called.               
  (home)))) ;; controll goes back to home function/handler.             (defroutes home-routes
(GET "/" [] (home)) (POST "/" [name content] (save-content name content)))

Views

As mentioned earlier, the view layer contains only logic for layout common for all pages and the individual pages are defined in the individual handlers defined for each individual route in the routes name-space. The “handler” performs  both the roles played by controller and view in MVC frameworks. The handler processes HTTP requests from the client and dispatches actions based on them. The handlers drive the model that’s responsible for handling the domain logic. It also returns a HTML mark-up like a view

The code for layout.clj is given below:

/views/layout.clj
(ns    lrn_comp.views.layout
(:require [hiccup.page :refer [html5 include-css]])) ;;hiccup template is used

 


(defn common [& body]
(html5 [:head [:title "Welcome to lrn_comp"] (include-css "/css/screen.css")] [:body body]))

The common layout declaration takes care of generating the base page template for us. The common layout adds the head and title tags, includes resources such as CSS, and appends the content to the body. Since the content is wrapped using the html5 macro, an HTML string is automatically generated from the content when the common layout is called. This handler will then serve the string back to the client. Any time we create a page we’ll simply wrap its body with the layout.

Conclusion

For a minimal Clojure web application the lein compojure-app template is sufficient. But as in any other template only boilerplate is generated and we have to code the file for application logic in the routes namespace. For database interaction we have to add clojure.java.jdbc library and the relvant jdbc driver to the dependencies in the configuration file. In the model namespace we have to code the file describing data. The app handler and the other generated files can be used as it is. Unlike in an opinionated framework like Phoenix, you can not generate application logic in a Clojure web app. Luminus is at-best a mini-framework making use of many libraries to provide many more functionalities. Even when you use Luminus, only boilerpate and model file to interact with the db is generated. You have to code application logic.








Added on June 28, 2017 Comment

Comments

#1

Vaas commented on June 30, 2017 at 4:32 p.m.

Thanks for sharing the article

#2

Kelly P commented on July 2, 2017 at 2:04 p.m.

This is the excellent website for the techies and developers. I found recently.

Post a comment