NOTE: funkyweb is WIP and is not production ready
funkyweb is a clojure web framework with route inference. It uses the name of the controller and actions to generate routes and url helpers.
Controllers are defined with the defcontroller macro and takes a name followed by any number of actions.
; generates the base route /dashboard
(defcontroller dashboard
... actions ...)
Controllers can also be nested using ->
; generates the base route /blog/posts
(defcontroller blog->posts
... actions ...)
And they can contain dynamic parts
; generates the base route /blog/:id/posts
(defcontroller blog->:id->posts
... actions ...)
Actions are defined using the GET PUT POST or DELETE macros and normally reside within a controller
(defcontroller dashboard
; generates the route /dashboard/index
(GET index []
"Welcome to your dashboard))
but can be standalone as well
; generates the route /index
(GET index []
"This is the index")
Actions defined with the GET macro responds to HTTP GET requests
; curl http://localhost:8080/index
; => this is the index action
(GET index [] "this is the index action")
; curl http://localhost:8080/say-hello/foo
; => Hello, foo!
(GET say-hello [name] (str "Hello, " name "!"))
Actions defined with the POST macro responds to HTTP POST requests
; curl -d "" http://localhost:8080/index
; => this is the index action
(POST index [] "this is the index action")
; curl -d "name=foo" http://localhost:8080/say-hello
; => Hello, foo!
(POST say-hello [name] (str "Hello, " name "!"))
Actions defined with the PUT macro responds to HTTP PUT requests, either real PUT's or fake ones with the _method hack.
; curl -d "_method=put" http://localhost:8080/index
; => this is the index action
(PUT index [] "this is the index action")
; curl -d "name=foo" -d "_method=put" http://localhost:8080/say-hello
; => Hello, foo!
(PUT say-hello [name] (str "Hello, " name "!"))
Actions defined with the DELETE macro responds to HTTP DELETE requests, either real DELETE's or fake ones with the _method hack.
; curl -d "_method=delete" http://localhost:8080/index
; => this is the index action
(DELETE index [] "this is the index action")
; curl -d "name=foo" -d "_method=delete" http://localhost:8080/say-hello
; => Hello, foo!
(DELETE say-hello [name] (str "Hello, " name "!"))
; curl http://localhost:8080/numbers/10
; => first number: 10 more:
; curl http://localhost:8080/numbers/10/20/30
; => first number: 10 more: (20 30)
(GET numbers [first-number & more]
(str "first number: " first-number " more: " more))
By using type hints funkyweb converts the parameters for you before your action is called and will return a 404 if the parameters don't match their specified types
(defcontroller dashboard
; curl http://localhost:8080/add/foo/2.3/4.6
; => 404 - not found
; curl http://localhost:8080/add/2.0/2.3/4.6
; => 404 - not found
; curl http://localhost:8080/add/2/2.3/4.6
; => 8.900000190734863
; curl http://localhost:8080/add/2/2/4
; => 8.0
(GET add [:int a :float b :double c]
(str (+ a b c))
When you define an action a function of the same name and arguments is also generated which when called returns the url for the action
(GET show [id]
(str "id: " id))
(show 10) ;=> /show/10
These functions also take an optional map of query-string parameters
(GET show [id] (str "id: " id))
(show 10 {:foo "bar"}) ;=> /show/10?foo=bar
What you return from an action controls what the response will look like.
Returning a string will render a response with the status set to 200 and the content-type set to text/html
; {:status 200 :headers {:content-type "text/html} :body "foo"}
(GET with-string []
"foo")
Returning an integer will render a response with the status set to the integer you returned and the content-type set to text/html
; {:status 404 :headers {:content-type "text/html} :body "404 - not found"}
(GET with-int []
404)
Returning a vector of status-code, content-type and body will build a response composed of those values
; {:status 200 :headers {:content-type "text/xml} :body "foo"}
(GET with-vector []
[200 "text/xml" "foo"]
Returning a map gives you full control over the response
; {:status 200 :headers {:content-type "text/html} :body "foo"}
(GET with-map []
{:status 200 :headers {:content-type "text/html} :body "foo"})
The error function let's you create custom error handlers, it takes a status code and a body
(error 404
"404 - not found")
(error 500
"500 - internal server error")
The helpers namespace contains aliases to the request, cookies and session namespaces and some convenience functions
respond-with let's your actions return different content based on the url
; GET /index, GET /index.html
; => <h1>HTML</h1>
; GET /index.xml
; => <response>xml</response>
; GET /index.json
; => {"response" : "json"}
(GET index []
(respond-with :html "<h1>HTML</h1>"
:xml "<response>xml</response>"
:json "{\"response\" : \"json\"}"
redirect-to takes either a string or a url generating function
(GET say-hello [name] (str "Hello, " name "!"))
; Redirects the user to /say-hello/foo
(redirect-to say-hello "foo")
; Redirects the user to http://google.se
(redirect-to "http://google.se")
request-get
; GET /index.json
; => content-type is: application/json
(GET index []
(str "content-type is: " (request-get :content-type)))
query-string, qs
; GET /index?foo=bar
; => foo = bar
(GET index []
(str "foo = " (query-string :foo)))
flash-set
(flash-set :notice "this is a notice")
flash-get
(flash-get :notice)
cookies-set
-
takes a key and a value and any number of configuration options and sets a cookie for the path / that expires at the end of the session
(cookies-set :foo "bar") (cookies-set :foo "bar" :expires "Fri, 31-Dec-2010 23:59:59 GMT")
cookies-get
(cookies-get :foo)
session-set
-
takes any number of key value pairs and associates them with the session
(session-set :foo "bar" :baz "quux")
session-get
(session-get :foo)
server
-
takes an adapter function, eg run-jetty, and an optional options hash and starts a server on port 8080 with :join? set to false
(server run-jetty)
(server run-jetty {:port 9090 :join? true})
(ns myapp
(:use funkyweb.controller
ring.adapter.jetty)
(:require [funkyweb.server :as server ]
[funkyweb.helpers :as helpers]))
(defcontroller hello-world
(GET say-hello [name]
(str "Hello, " name "!"))
(GET hello-world []
(helpers/redirect-to say-hello "world")))
(server/server run-jetty)
If you are using leiningen add the following to your project.clj:
:dependencies [[org.clojure/clojure "1.2.0"]
[org.clojure/clojure-contrib "1.2.0"]
[funkyweb "0.1.0"]]
Copyright (C) 2010 Patrik Hedman
Distributed under the Eclipse Public License, the same as Clojure.