A maximalist web framework for lisp aficionados

Use the janet programming language to build web apps faster with less code and very low memory usage.

(import joy :prefix "")

(route :get "/" :home)

(defn home [request]
  (text/plain "You found joy!"))

(def app (app))

(server app 9001)

Installation on macOS

Janet can be installed with homebrew:
brew install janet
Then to install joy:
jpm install joy
More installation options for janet here

HTML is data

Joy uses hiccup syntax to render html. There is no separate template language to learn! You have all of janet's power when writing html!
  [:input {:type "text" :placeholder "Your name"}]
  [:input {:type "email" :placeholder "Your email"}]
  [:input {:type "submit" :value "Submit"}]]
  <input type="text" placeholder="Your name" />
  <input type="email" placeholder="Your email" />
  <input type="submit" value="Submit" />
Use html inside of janet, not the other way around
(let [groceries [{:name "eggs"}
                 {:name "milk"}
                 {:name "tomatoes"}]]
    (foreach [g groceries]
      [:li (g :name)])])
There is a shorthand for css classes too! Great for atomic css!
  "You found joy!"]
<h1 class="text-2xl text-gray-400">You found joy!</h1>
The real beauty of having a regular old data structure represent your html is that you can assign it to a variable and create your own names without changing the rendered html!
  (def h1 :h1.text-2xl.text-gray-400)

  [h1 "You found joy!"]
<h1 class="text-2xl text-gray-400">You found joy!</h1>

Everything is a function

Joy doesn't uses objects or classes. Everything is a function that takes a request and outputs a response
(use joy)

(route :get "/posts" :index)
(route :get "/posts/new" :new)
(route :post "/posts" :create)

(def body
  (body :posts
    (validates [:title :body] :required true)
    (permit :title :body)))

(defn index [request]
  (let [posts (db/from :posts)]

     (foreach [p posts]
       [:h1 (p :title)]
       [:p (p :body)]])]))

(defn new [request &opt errors]
  (let [post (body request)]

    [:form {:method :post :action "/posts"}
      [:input {:type "text" :name "name" :value (post :name)}]
       (errors :name)]

      [:textarea {:name "body"}
        (post :body)]
       (errors :body)]

      [:input {:type "submit" :value "Save"}]]))

(defn create [request]
  (let [post (-> (body request)

    (if (saved? post)
      (redirect-to :home)
      (new request (errors post)))))