Compojure

Личный сайт Go-разработчика из Казани

Getting Started with Compojure

Compojure is a DSL for quickly creating performant web applications in Clojure with minimal effort:

1(ns myapp.core 2 (:require [compojure.core :refer :all] 3 [org.httpkit.server :refer [run-server]])) ; httpkit is a server 4 5(defroutes myapp 6 (GET "/" [] "Hello World")) 7 8(defn -main [] 9 (run-server myapp {:port 5000}))

Step 1: Create a project with Leiningen:

lein new myapp

Step 2: Put the above code in src/myapp/core.clj

Step 3: Add some dependencies to project.clj:

[compojure "1.1.8"]
[http-kit "2.1.16"]

Step 4: Run:

lein run -m myapp.core

View at: http://localhost:5000/

Compojure apps will run on any ring-compatible server, but we recommend http-kit for its performance and massive concurrency.

Routes

In compojure, each route is an HTTP method paired with a URL-matching pattern, an argument list, and a body.

1(defroutes myapp 2 (GET "/" [] "Show something") 3 (POST "/" [] "Create something") 4 (PUT "/" [] "Replace something") 5 (PATCH "/" [] "Modify Something") 6 (DELETE "/" [] "Annihilate something") 7 (OPTIONS "/" [] "Appease something") 8 (HEAD "/" [] "Preview something"))

Compojure route definitions are just functions which accept request maps and return response maps:

1(myapp {:uri "/" :request-method :post}) 2; => {:status 200 3; :headers {"Content-Type" "text/html; charset=utf-8} 4; :body "Create Something"}

The body may be a function, which must accept the request as a parameter:

1(defroutes myapp 2 (GET "/" [] (fn [req] "Do something with req")))

Or, you can just use the request directly:

1(defroutes myapp 2 (GET "/" req "Do something with req"))

Route patterns may include named parameters:

1(defroutes myapp 2 (GET "/hello/:name" [name] (str "Hello " name)))

You can adjust what each parameter matches by supplying a regex:

1(defroutes myapp 2 (GET ["/file/:name.:ext" :name #".*", :ext #".*"] [name ext] 3 (str "File: " name ext)))

Middleware

Clojure uses Ring for routing. Handlers are just functions that accept a request map and return a response map (Compojure will turn strings into 200 responses for you).

You can easily write middleware that wraps all or part of your application to modify requests or responses:

1(defroutes myapp 2 (GET "/" req (str "Hello World v" (:app-version req)))) 3 4(defn wrap-version [handler] 5 (fn [request] 6 (handler (assoc request :app-version "1.0.1")))) 7 8(defn -main [] 9 (run-server (wrap-version myapp) {:port 5000}))

Ring-Defaults provides some handy middlewares for sites and apis, so add it to your dependencies:

[ring/ring-defaults "0.1.1"]

Then, you can import it in your ns:

(ns myapp.core
  (:require [compojure.core :refer :all]
            [ring.middleware.defaults :refer :all]
            [org.httpkit.server :refer [run-server]]))

And use wrap-defaults to add the site-defaults middleware to your app:

(defn -main []
  (run-server (wrap-defaults myapp site-defaults) {:port 5000}))

Now, your handlers may utilize query parameters:

1(defroutes myapp 2 (GET "/posts" req 3 (let [title (get (:params req) :title) 4 author (get (:params req) :author)] 5 (str "Title: " title ", Author: " author))))

Or, for POST and PUT requests, form parameters as well

1(defroutes myapp 2 (POST "/posts" req 3 (let [title (get (:params req) :title) 4 author (get (:params req) :author)] 5 (str "Title: " title ", Author: " author))))

Return values

The return value of a route block determines the response body passed on to the HTTP client, or at least the next middleware in the ring stack. Most commonly, this is a string, as in the above examples. But, you may also return a response map:

1(defroutes myapp 2 (GET "/" [] 3 {:status 200 :body "Hello World"}) 4 (GET "/is-403" [] 5 {:status 403 :body ""}) 6 (GET "/is-json" [] 7 {:status 200 :headers {"Content-Type" "application/json"} :body "{}"}))

Static Files

To serve up static files, use compojure.route.resources. Resources will be served from your project’s resources/ folder.

1(require '[compojure.route :as route]) 2 3(defroutes myapp 4 (GET "/") 5 (route/resources "/")) ; Serve static resources at the root path 6 7(myapp {:uri "/js/script.js" :request-method :get}) 8; => Contents of resources/public/js/script.js

Views / Templates

To use templating with Compojure, you’ll need a template library. Here are a few:

Stencil

Stencil is a Mustache template library:

1(require '[stencil.core :refer [render-string]]) 2 3(defroutes myapp 4 (GET "/hello/:name" [name] 5 (render-string "Hello {{name}}" {:name name})))

You can easily read in templates from your resources directory. Here’s a helper function

1(require 'clojure.java.io) 2 3(defn read-template [filename] 4 (slurp (clojure.java.io/resource filename))) 5 6(defroutes myapp 7 (GET "/hello/:name" [name] 8 (render-string (read-template "templates/hello.html") {:name name})))

Selmer

Selmer is a Django and Jinja2-inspired templating language:

1(require '[selmer.parser :refer [render-file]]) 2 3(defroutes myapp 4 (GET "/hello/:name" [name] 5 (render-file "templates/hello.html" {:name name})))

Hiccup

Hiccup is a library for representing HTML as Clojure code

1(require '[hiccup.core :as hiccup]) 2 3(defroutes myapp 4 (GET "/hello/:name" [name] 5 (hiccup/html 6 [:html 7 [:body 8 [:h1 {:class "title"} 9 (str "Hello " name)]]])))

Markdown

Markdown-clj is a Markdown implementation.

1(require '[markdown.core :refer [md-to-html-string]]) 2 3(defroutes myapp 4 (GET "/hello/:name" [name] 5 (md-to-html-string "## Hello, world")))

Further reading: