From c040acba1a8c389d1e4da7332eb4ba2cc58d7edb Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 Sep 2014 22:56:25 +0200 Subject: Added compojure --- compojure.html.markdown | 225 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 compojure.html.markdown (limited to 'compojure.html.markdown') diff --git a/compojure.html.markdown b/compojure.html.markdown new file mode 100644 index 00000000..56f43cb7 --- /dev/null +++ b/compojure.html.markdown @@ -0,0 +1,225 @@ +--- +category: tool +tool: compojure +contributors: + - ["Adam Bard", "http://adambard.com/"] +filename: learncompojure.clj +--- + +## Getting Started with Compojure + +Compojure is a DSL for *quickly* creating *performant* web applications +in Clojure with minimal effort: + +```clojure +(ns myapp.core + (:require [compojure.core :refer :all] + [org.httpkit.server :refer [run-server]])) ; httpkit is a server + +(defroutes myapp + (GET "/" [] "Hello World")) + +(defn -main [] + (run-server myapp {:port 5000})) +``` + +Create a project with [Leiningen](http://leiningen.org/): + +``` +lein new myapp +``` + +Add your dependencies: + +``` +[compojure "1.1.8"] +[http-kit "2.1.16"] +``` + +And run: + +``` +lein run -m myapp.core +``` + +View at: + +Compojure apps will run on any ring-compatible server, but we recommend +[http-kit](http://http-kit.org/) for its performance and +[massive concurrency](http://http-kit.org/600k-concurrent-connection-http-kit.html). + +### Routes + +In compojure, each route is an HTTP method paired with a URL-matching pattern, +an argument list, and a body. + +```clojure +(defroutes myapp + (GET "/" [] "Show something") + (POST "/" [] "Create something") + (PUT "/" [] "Replace something") + (PATCH "/" [] "Modify Something") + (DELETE "/" [] "Annihilate something") + (OPTIONS "/" [] "Appease something") + (HEAD "/" [] "Preview something")) +``` + +Compojure route definitions are just functions which +[accept request maps and return response maps](https://github.com/mmcgrana/ring/blob/master/SPEC): + +```clojure +(myapp {:uri "/" :request-method :post}) +; => {:status 200 +; :headers {"Content-Type" "text/html; charset=utf-8} +; :body "Create Something"} +``` + +The body may be a function, which must accept the request as a parameter: + +```clojure +(defroutes myapp + (GET "/" [] (fn [req] "Do something with req"))) +``` + +Route patterns may include named parameters, + +```clojure +(defroutes myapp + (GET "/hello/:name" [name] (str "Hello " name))) +``` + +You can match entire paths with * + +```clojure +(defroutes myapp + (GET "/file/*.*" [*] (str *))) +``` + +Handlers may utilize query parameters: + +```clojure +(defroutes myapp + (GET "/posts" [] + (fn [req] + (let [title (get (:params req) "title") + author (get (:params req) "title")] + " Do something with title and author")))) +``` + +Or, for POST and PUT requests, form parameters + +```clojure +(defroutes myapp + (POST "/posts" [] + (fn [req] + (let [title (get (:params req) "title") + author (get (:params req) "title")] + "Do something with title and author")))) +``` + + +### Return values + +The return value of a route block determines at least 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 body](https://github.com/mmcgrana/ring/blob/master/SPEC): + +```clojure +(defroutes myapp + (GET "/" [] + {:status 200 :body "Hello World"}) + (GET "/is-403" [] + {:status 403 :body ""}) + (GET "/is-json" [] + {: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. + +```clojure +(require '[compojure.route :as route]) + +(defroutes myapp + (GET "/") + (route/resources "/")) ; Serve static resources at the root path + +(myapp {:uri "/js/script.js" :request-method :get}) +; => 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](https://github.com/davidsantiago/stencil) + +[Stencil](https://github.com/davidsantiago/stencil) is a [Mustache](http://mustache.github.com/) template library: + +```clojure +(require '[stencil.core :refer [render-string]]) + +(defroutes myapp + (GET "/hello/:name" [name] + (render-string "Hello {{name}}" {:name name}))) +``` + +You can easily read in templates from your resources directory. Here's a helper function + +```clojure +(require 'clojure.java.io) + +(defn read-template [filename] + (slurp (clojure.java.io/resource filename))) + +(defroutes myapp + (GET "/hello/:name" [name] + (render-string (read-template "templates/hello.html") {:name name}))) +``` + +#### [Selmer](https://github.com/yogthos/Selmer) + +[Selmer](https://github.com/yogthos/Selmer) is a Django and Jinja2-inspired templating language: + +```clojure +(require '[selmer.parser :refer [render-file]]) + +(defroutes myapp + (GET "/hello/:name" [name] + (render-file "templates/hello.html" {:name name}))) +``` + +#### [Hiccup](https://github.com/weavejester/hiccup) + +[Hiccup](https://github.com/weavejester/hiccup) is a library for representing HTML as Clojure code + +```clojure +(require '[hiccup.core :as hiccup]) + +(defroutes myapp + (GET "/hello/:name" [name] + (hiccup/html + [:html + [:body + [:h1 {:class "title"} + (str "Hello " name)]]]))) +``` + +#### [Markdown](https://github.com/yogthos/markdown-clj) + +[Markdown-clj](https://github.com/yogthos/markdown-clj) is a Markdown implementation. + +```clojure +(require '[markdown.core :refer [md-to-html-string]]) + +(defroutes myapp + (GET "/hello/:name" [name] + (md-to-html-string "## Hello, world"))) +``` + +Further reading: + +[Clojure for the Brave and True](http://www.braveclojure.com/) -- cgit v1.2.3 From 014262f5c62ae1904b5e70135aca8c56540abc78 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 6 Sep 2014 10:18:31 +0200 Subject: Updated compojure per weavejester's suggestions --- compojure.html.markdown | 60 ++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 25 deletions(-) (limited to 'compojure.html.markdown') diff --git a/compojure.html.markdown b/compojure.html.markdown index 56f43cb7..1644dfb7 100644 --- a/compojure.html.markdown +++ b/compojure.html.markdown @@ -81,18 +81,26 @@ The body may be a function, which must accept the request as a parameter: (GET "/" [] (fn [req] "Do something with req"))) ``` -Route patterns may include named parameters, +Or, you can just use the request directly: + +```clojure +(defroutes myapp + (GET "/" req "Do something with req")) +``` + +Route patterns may include named parameters: ```clojure (defroutes myapp (GET "/hello/:name" [name] (str "Hello " name))) ``` -You can match entire paths with * +You can adjust what each parameter matches by supplying a regex: ```clojure (defroutes myapp - (GET "/file/*.*" [*] (str *))) + (GET ["/file/:name.:ext" :name #".*", :ext #".*"] [name ext] + (str "File: " name ext)) ``` Handlers may utilize query parameters: @@ -100,10 +108,10 @@ Handlers may utilize query parameters: ```clojure (defroutes myapp (GET "/posts" [] - (fn [req] - (let [title (get (:params req) "title") - author (get (:params req) "title")] - " Do something with title and author")))) + (fn [req] + (let [title (get (:params req) "title") + author (get (:params req) "title")] + " Do something with title and author")))) ``` Or, for POST and PUT requests, form parameters @@ -111,10 +119,10 @@ Or, for POST and PUT requests, form parameters ```clojure (defroutes myapp (POST "/posts" [] - (fn [req] - (let [title (get (:params req) "title") - author (get (:params req) "title")] - "Do something with title and author")))) + (fn [req] + (let [title (get (:params req) "title") + author (get (:params req) "title")] + "Do something with title and author")))) ``` @@ -123,16 +131,16 @@ Or, for POST and PUT requests, form parameters The return value of a route block determines at least 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 body](https://github.com/mmcgrana/ring/blob/master/SPEC): +But, you may also return a [response map](https://github.com/mmcgrana/ring/blob/master/SPEC): ```clojure (defroutes myapp (GET "/" [] - {:status 200 :body "Hello World"}) + {:status 200 :body "Hello World"}) (GET "/is-403" [] - {:status 403 :body ""}) + {:status 403 :body ""}) (GET "/is-json" [] - {:status 200 :headers {"Content-Type" "application/json"} :body "{}"})) + {:status 200 :headers {"Content-Type" "application/json"} :body "{}"})) ``` ### Static Files @@ -164,7 +172,7 @@ To use templating with Compojure, you'll need a template library. Here are a few (defroutes myapp (GET "/hello/:name" [name] - (render-string "Hello {{name}}" {:name name}))) + (render-string "Hello {{name}}" {:name name}))) ``` You can easily read in templates from your resources directory. Here's a helper function @@ -177,7 +185,7 @@ You can easily read in templates from your resources directory. Here's a helper (defroutes myapp (GET "/hello/:name" [name] - (render-string (read-template "templates/hello.html") {:name name}))) + (render-string (read-template "templates/hello.html") {:name name}))) ``` #### [Selmer](https://github.com/yogthos/Selmer) @@ -189,7 +197,7 @@ You can easily read in templates from your resources directory. Here's a helper (defroutes myapp (GET "/hello/:name" [name] - (render-file "templates/hello.html" {:name name}))) + (render-file "templates/hello.html" {:name name}))) ``` #### [Hiccup](https://github.com/weavejester/hiccup) @@ -201,11 +209,11 @@ You can easily read in templates from your resources directory. Here's a helper (defroutes myapp (GET "/hello/:name" [name] - (hiccup/html - [:html - [:body - [:h1 {:class "title"} - (str "Hello " name)]]]))) + (hiccup/html + [:html + [:body + [:h1 {:class "title"} + (str "Hello " name)]]]))) ``` #### [Markdown](https://github.com/yogthos/markdown-clj) @@ -217,9 +225,11 @@ You can easily read in templates from your resources directory. Here's a helper (defroutes myapp (GET "/hello/:name" [name] - (md-to-html-string "## Hello, world"))) + (md-to-html-string "## Hello, world"))) ``` Further reading: -[Clojure for the Brave and True](http://www.braveclojure.com/) +* [Official Compojure Documentation](https://github.com/weavejester/compojure/wiki) + +* [Clojure for the Brave and True](http://www.braveclojure.com/) -- cgit v1.2.3 From c0d8a18b08d114dd0142a09f6787c7221b423802 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 6 Sep 2014 10:28:58 +0200 Subject: Made beginning easier --- compojure.html.markdown | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'compojure.html.markdown') diff --git a/compojure.html.markdown b/compojure.html.markdown index 1644dfb7..96555273 100644 --- a/compojure.html.markdown +++ b/compojure.html.markdown @@ -23,20 +23,22 @@ in Clojure with minimal effort: (run-server myapp {:port 5000})) ``` -Create a project with [Leiningen](http://leiningen.org/): +**Step 1:** Create a project with [Leiningen](http://leiningen.org/): ``` lein new myapp ``` -Add your dependencies: +**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"] ``` -And run: +**Step 4:** Run: ``` lein run -m myapp.core -- cgit v1.2.3 From 3addfcf7148c8da62c3523de7fff7ea55d31084a Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 11 Sep 2014 16:17:06 +0200 Subject: Updated compojure tut --- compojure.html.markdown | 69 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 13 deletions(-) (limited to 'compojure.html.markdown') diff --git a/compojure.html.markdown b/compojure.html.markdown index 96555273..444c8c58 100644 --- a/compojure.html.markdown +++ b/compojure.html.markdown @@ -102,29 +102,72 @@ You can adjust what each parameter matches by supplying a regex: ```clojure (defroutes myapp (GET ["/file/:name.:ext" :name #".*", :ext #".*"] [name ext] - (str "File: " name ext)) + (str "File: " name ext))) ``` -Handlers may utilize query parameters: +### Middleware + +Clojure uses [Ring](https://github.com/ring-clojure/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: + +```clojure +(defroutes myapp + (GET "/" req (str "Hello World v" (:app-version req)))) + +(defn wrap-version [handler] + (fn [request] + (handler (assoc request :app-version "1.0.1")))) + +(defn -main [] + (run-server (wrap-version myapp) {:port 5000})) +``` + +[Ring-Defaults](https://github.com/ring-clojure/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: ```clojure (defroutes myapp - (GET "/posts" [] - (fn [req] - (let [title (get (:params req) "title") - author (get (:params req) "title")] - " Do something with title and author")))) + (GET "/posts" req + (let [title (get (:params req) "title") + author (get (:params req) "title")] + (str "Title: " title ", Author: " author)))) ``` -Or, for POST and PUT requests, form parameters +Or, for POST and PUT requests, form parameters as well ```clojure (defroutes myapp - (POST "/posts" [] - (fn [req] - (let [title (get (:params req) "title") - author (get (:params req) "title")] - "Do something with title and author")))) + (POST "/posts" req + (let [title (get (:params req) "title") + author (get (:params req) "title")] + (str "Title: " title ", Author: " author)))) ``` -- cgit v1.2.3 From d855f4a156a95fe8eaf8d7946db6aed55ebabf66 Mon Sep 17 00:00:00 2001 From: Matthias Nehlsen Date: Fri, 12 Sep 2014 19:33:03 +0200 Subject: Update compojure.html.markdown I think you meant different parameters for title and author --- compojure.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'compojure.html.markdown') diff --git a/compojure.html.markdown b/compojure.html.markdown index 444c8c58..c5b03498 100644 --- a/compojure.html.markdown +++ b/compojure.html.markdown @@ -156,7 +156,7 @@ Now, your handlers may utilize query parameters: (defroutes myapp (GET "/posts" req (let [title (get (:params req) "title") - author (get (:params req) "title")] + author (get (:params req) "author")] (str "Title: " title ", Author: " author)))) ``` @@ -166,7 +166,7 @@ Or, for POST and PUT requests, form parameters as well (defroutes myapp (POST "/posts" req (let [title (get (:params req) "title") - author (get (:params req) "title")] + author (get (:params req) "author")] (str "Title: " title ", Author: " author)))) ``` -- cgit v1.2.3 From c67cd5bb4be6f972104a82f27fa75f18e0677de9 Mon Sep 17 00:00:00 2001 From: Matthias Nehlsen Date: Fri, 12 Sep 2014 19:36:53 +0200 Subject: Update compojure.html.markdown While I'm at it... great work, by the way. The article about Compojure is really helpful. Thanks for that --- compojure.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'compojure.html.markdown') diff --git a/compojure.html.markdown b/compojure.html.markdown index 444c8c58..af97a147 100644 --- a/compojure.html.markdown +++ b/compojure.html.markdown @@ -173,7 +173,7 @@ Or, for POST and PUT requests, form parameters as well ### Return values -The return value of a route block determines at least the response body +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](https://github.com/mmcgrana/ring/blob/master/SPEC): -- cgit v1.2.3 From 5d3e938574b74704801dcb61043a8603ac087f73 Mon Sep 17 00:00:00 2001 From: Bobby Wilson Date: Sat, 30 May 2015 11:43:37 -0600 Subject: Use keywords in POST/PUT examples Using strings doesn't work for the POST/PUT examples here, changing them to keywords fixes this --- compojure.html.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'compojure.html.markdown') diff --git a/compojure.html.markdown b/compojure.html.markdown index 36a8d123..32181e26 100644 --- a/compojure.html.markdown +++ b/compojure.html.markdown @@ -155,8 +155,8 @@ Now, your handlers may utilize query parameters: ```clojure (defroutes myapp (GET "/posts" req - (let [title (get (:params req) "title") - author (get (:params req) "author")] + (let [title (get (:params req) :title) + author (get (:params req) :author)] (str "Title: " title ", Author: " author)))) ``` @@ -165,8 +165,8 @@ Or, for POST and PUT requests, form parameters as well ```clojure (defroutes myapp (POST "/posts" req - (let [title (get (:params req) "title") - author (get (:params req) "author")] + (let [title (get (:params req) :title) + author (get (:params req) :author)] (str "Title: " title ", Author: " author)))) ``` -- cgit v1.2.3