A full stack web framework written in janet
Ch-ch-changes!
First, I want to give a shout out to
@goto-engineering for fixing the response duration logging code! @hammywhammy / @hweeks for all of the hard work on the docker/ci stuff!
Tired of having to define all of your routes in two different files, me too!
(route :get "/" :home)
(defn home [r])
This is how the new template works too:
(route :get "/todos" :todos/index)
(route :get "/todos/new" :todos/new)
(route :get "/todos/:id" :todos/show)
(route :post "/todos" :todos/create)
(route :get "/todos/:id/edit" :todos/edit)
(route :patch "/todos/:id" :todos/patch)
(route :delete "/todos/:id" :todos/delete)
You can imagine the corresponding functions.
Before:
(def routes (routes [:get "/" :home]))
(def app (as-> (handler routes) ?
(layout ? layout/app)
(csrf-token ?)
(session ?)
(extra-methods ?)
(query-string ?)
(body-parser ?)
(server-error ?)
(x-headers ?)
(static-files ?)
(not-found ?)
(logger ? )))
After:
(def routes (routes [:get "/" :home]))
(def app (app {:routes routes :layout layout-fn}))
You can also turn middleware on/off by changing the dictionary passed to app:
(app {:layout false :extra-methods false :session false :x-headers false :static-files false})
There are a few more options too like changing the cookie options for sessions, things like that.
Don't want to bother writing a whole middleware function just to append things to the request on certain routes? Me neither!
(before "/*" :run-before)
(defn run-before [req]
(put req :a 1))
You can use a combination of wildcard routes and the before function to modify the request dictionary before any matching routes. Make sure you return the request from your before functions.
Similarly, the after
function works the same way, except on the response, you also need to return the response as well.
.
├── Procfile
├── main.janet
├── project.janet
└── public
├── app.css
└── app.js
5 files with a Procfile! One thing I didn't add to the default template is a Dockerfile which I'm kind of fine tuning still, it works great with dokku!
That's it for now, there are a few more new things, but you can find me online anywhere or consult the docs if you want to know more
Warning Breaking changes ahead, only if you're using the css
, js
or app
functions
I try not to make too many breaking changes, but it's still early days and hopefully no one was using the js/css bundler stuff.
Also I don't plan on introducing breaking changes EVER after 1.0
In fact my strategy is if I want to break things too much, I will release a whole new repo named joy2.
Let's hope it never comes to that.
Anyway if you were using those functions change this:
(css "/style1.css" "/style2.css")
to this
(link {:href ["/style1.css" "/style2.css"]})
and the js is similar:
(js "/js1.js" "/js2.js")
to:
(script {:src ["/js1.js" "/js2.js"]})
with the added benefit now of adding other attributes, like :defer
Again, this doesn't happen often (or at all) but sometimes a new feature is just too good to pass up. If you are using the app function, it has changed to handlers, so this:
(app (handler routes1) (handler routes2))
is now this:
(handlers (handler routes1) (handler routes2))
What do you get from this breaking change?
Let me show you something really cool:
(use joy)
(defn / [request]
(text/html
[:h1 "You found joy!"]))
(def app (app))
(server app 9001) # go ahead and visit http://localhost:9001 I dare ya
That is all it takes now to get a joy app up and running!
A few bugfixes, notably though, headers are case-insensitive now with a new, handy headers
function:
(header request :x-csrf-token) # or whatever you want
Oh! There's also a new json-body-parser
middleware that parses incoming json requests with a content-type: application/json
header
Tiny release, just fixing up html escaping
Notable things in this release:
routes/
folder like so:(use joy)
(defroutes routes
[:get "/" :home/index])
Assuming you have a file: src/routes/home.janet
and then within that file, this code:
(defn index [request])
[:input {:type "text" :name "tag[]"}]
[:input {:type "text" :name "tag[]"}]
[:input {:type "text" :name "tag[]"}]
and on the server you'll have your body look like this:
(defn a-post [request]
(def body (request :body))
(body :tag)) # => @["tag1" "tag2" "tag3"]
Here's the full commit log for the curious:
This one was a doozy, but I'm fairly sure there were no breaking changes 🤞
rescue-from
fnwith-db-connection
dyn
for the database connection instead of opening/closing a connection on each requestserver-error
middleware would totally ruin the handler's janet envIt doesn't seem like a lot, but it is. Here's the gist of it:
Before:
(import joy :prefix "")
(defn index [request]
(let [{:db db} request]
(fetch-all db [:todos])))
Now:
(import joy :prefix "")
(import joy/db)
(db/connect)
(defn index [request]
(db/fetch-all [:todos])))
This change sets joy up for it's own console a la joy console
from the terminal similar to rails console
which sets up the database connection and lets you mess around with data.
\0
charIt went from this
(label :field-name)
to this
(label :field-name "label string")
so watch out.
This:
(submit "save" [:class "red"])
to this:
(submit "save" :class "red")
Also changed, it's now:
(with-db-connection [db "dev.sqlite3"]
(delete-all db :post :where {:draft true} :limit 1))
or
(delete-all db :post)
Which will delete all rows in the post
table.
Check if static files exist in middleware first so they actually return 404s