Routes enable the outside world to interact with your app via URLs.
Routes are defined inside of the src/routes.clj
file.
The most basic route definition requires an http method, a url and a "pointer" to a function:
; src/routes.clj
(ns routes
(:require [coast]))
(defn home [request])
{:status 200 :body "hello world!" :headers {"content-type" "text/html"}}
(def routes
(coast/site
[:get "/" ::home]))
The return value of the function will be sent back to the client as a response.
You'll mostly be "binding" a route to a function using a :namespace/function
signature:
[:get "/" :post/index]
The above keyword :post/index
refers to the src/post.clj
file's index
function.
Resourceful routes use different HTTP verbs to indicate the type of request:
[:get url :qualified/keyword]
[:post url :qualified/keyword]
[:put url :qualified/keyword]
[:patch url :qualified/keyword]
[:delete url :qualified/keyword]
Route parameters are defined like so:
(defn view [{:keys [params]}])
{:status 200
:body (str "post " (:id params))}
[:get "/posts/:id" ::view]
In the example above, :id
is a route parameter.
Its value is then retrieved via the :params
keyword.
Though routes are defined inside the src/routes.clj
file, they are referenced everywhere else in the application (e.g. using the url-for
route helper to make a URL for a given route).
By using the last element of the vector, you can assign your route a unique name:
[:get "/posts" :post/index :posts]
This will enable you to use route
helpers in your code, like so:
; before
[:a {:href "/posts"} "List of posts"]
; after
[:a {:href (url-for :posts)} "List of posts"]
; you can also call the original name of the function as well
[:a {:href (url-for :post/index)} "List of posts"]
; src/post.clj
(ns post
(:require [coast]))
(defn index [request]
(coast/redirect-to :posts))
; or more verbose
(defn index [request]
(coast/redirect (coast/url-for :posts))
Both route
helpers share the same signature and accept an optional parameters map as their second argument:
[:get "/posts/:id" :post/view :post]
(url-for :post {:id 1})
(redirect-to :post {:id 1})
Namespaced keywords are supported as well
[:get "/authors/:author-id/posts/:id" :post/view]
(url-for :post/view {:author/id 1 :id 2})
(redirect-to :post/view {:author/id 1 :id 2})
; or you can use the exact parameter name with a - instead of a /
(url-for :post/view {:author-id 1 :id 2})
Anything you pass to url-for
or redirect-to
that isn't defined as a route parameter will be appended as a query parameter
[:get "/posts/:post-id/comments/:id/edit" :comment/edit]
(url-for :comment/edit {:post/id 1 :id 2 :all true}) ; => "/post/1/comment/2/edit?all=true"
Routes don't have to just respond with hiccup vectors, they can also respond with a map which overrides any layouts or the coast default of rendering with html.
(defn json [request]
{:status 200 :body {:message "ok"} :headers {"content-type" "application/json"}}) ; this responds with json
(defn json [request]
(coast/ok {:message "ok"} :json)) ; same as above, but shorter
(coast/api
[:get "/" ::json)])
You can define separate routes for an api and a site:
(ns your-app
(:require [coast]))
(def routes
(coast/routes
; this route corresponds to the src/site/home.clj index function
(coast/site
[:get "/" :home/index :site.home/index])
; these routes correspond to the src/api/home.clj index and status functions
(coast/api
[:get "/api" :api.home/index]
[:get "/api/status" :api.home/status])))
(def app (coast/app {:routes routes}))
Coast uses a different set of middleware functions when responding to an api request vs a site request.
The api
routes do not check for layouts and a host of other things, making them lighter-weight than their site counterparts.
You will often create resourceful routes to do CRUD operations on a resource.
resource
assigns CRUD routes to a namespace using a single line of code:
; This...
[:resource :post]
; ...equates to this:
[:get "/posts" :post/index]
[:get "/posts/build" :post/build]
[:post "/posts" :post/create]
[:get "/posts/:id" :post/view]
[:get "/posts/:id/edit" :post/edit]
[:put "/posts/:id" :post/change]
[:delete "/posts/:id" :post/delete]
NOTE: This feature is only available when binding routes to a namespace.
You can limit the routes assigned by the resource
method by using the except
or only
keywords
Removes GET resource/create
and GET resource/:id/edit
routes:
; src/routes.clj
[:resource :post :except [:create :edit])
Keeps only the passed routes:
; src/routes.clj
[:resource :post :only [:index :view])
You can wrap middleware around any resource as you would with a single route:
; src/routes.clj
(ns routes
(:require [coast]))
(defn auth [handler]
(fn [request]
(if (some? (:session request))
(handler request)
(coast/unauthorized [:h1 "HAL9000 says, \"Sorry Dave, I can't let you do that\""]))))
(coast/with auth
[:resource :post]))
If your application routes share common urls, instead of repeating the same urls for each route, you can prefix them like so:
; no prefix
[:get "/api/v1/members" :api.v1.members/index]
[:post "/api/v1/members" :api.v1.members/create]
; with prefix
(coast/prefix-routes "/api/v1"
[:get "/members"]
[:post "/members"])
Assign one or many middleware to the route group:
(coast/with auth
(coast/prefix-routes "/api/v1"
[:get "/members"
[:post "/members"]]))
NOTE: Route middleware executes after app middleware during the request and before app middleware during the response.
Route middleware also executes from top to bottom:
(ns routes
(:require [coast]
[middleware]))
(def routes
(coast/site
(coast/with middleware/set-title
(coast/with-layout :components/layout
[:get "/" :home/index]
[:get "/docs" :home/docs]
[:get "/docs/:doc.md" :home/doc]
[:get "/screencast" :home/screencast]
[:get "/sign-up" :member/build]
[:post "/members" :member/create]
[:get "/sign-in" :session/build]
[:post "/sessions" :session/create]
[:resource :invite :only [:build :create]]
(coast/with middleware/auth
[:get "/dashboard" :home/dashboard]
[:delete "/sessions" :session/delete]
[:resource :member :except [:index :view :build :create]]
[:resource :invite :except [:index :view :build :create]]
[:resource :post :only [:build :create :edit :change :delete]]
[:put "/invite/:invite-id/approve" :invite/approve])
[:resource :post :only [:view :index]]))
[:404 :home/not-found]
[:500 :home/server-error]))
In this example (from the coast docs site), the route middleware executes in this order:
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close