🃏 Jester 🃏
The sinatra-like web framework for Nim. Jester provides a DSL for quickly creating web applications in Nim.
# example.nim
import htmlgen
import jester
routes:
get "/":
resp h1("Hello world")
Compile and run with:
cd tests/example
nim c -r example.nim
View at: localhost:5000
Routes
routes:
get "/":
# do something here.
All routes must be inside a routes
block.
Routes will be executed in the order that they are declared. So be careful when halting.
The route path may contain a special pattern or just a static string. Special
patterns are almost identical to Sinatra's, the only real difference is the
use of @
instead of the :
.
get "/hello/@name":
# This matches "/hello/fred" and "/hello/bob".
# In the route ``@"name"`` will be either "fred" or "bob".
# This can of course match any value which does not contain '/'.
resp "Hello " & @"name"
The patterns in Jester are currently a bit more limited, there is no wildcard patterns.
You can use the '?' character to signify optional path parts.
get "/hello/@name?":
# This will match what the previous code example matches but will also match
# "/hello/".
if @"name" == "":
resp "No name received :("
else:
resp "Hello " & @"name"
In this case you might want to make the leading '/' optional too, you can do this by changing the pattern to "/hello/?@name?". This is useful because Jester will not match "/hello" if the leading '/' is not made optional.
Regex
Regex can also be used as a route pattern. The subpattern captures will be
placed in request.matches
when a route is matched. For example:
get re"^\/([0-9]{2})\.html$":
resp request.matches[0]
This will match URLs of the form /15.html
. In this case
request.matches[0]
will be 15
.
Conditions
Jester supports conditions, however they are limited to a simple cond
template.
routes:
get "/@name":
cond @"name" == "daniel"
# ``cond`` will pass execution to the next matching route if @"name" is not
# "daniel".
resp "Correct, my name is daniel."
get "/@name":
# This will be the next route that is matched.
resp "No, that's not my name."
Return values
Route bodies all have an implicit request
object. This object is documented
in jester.nim and documentation can be generated by executing nim doc jester.nim
.
Returning a response from a route should be done using one of the following functions:
- One of the
resp
functions. - By setting
body
,headers
and/orstatus
and callingreturn
. redirect
functionattachment
function
There might be more. Take a look at the documentation of jester.nim for more info.
Manual routing
It is possible not to use the routes
macro and to do the routing yourself.
You can do this by writing your own match
procedure. Take a look at
example2 for an example on how to do this.
Static files
By default Jester looks for static files in ./public
. This can be overriden
using the setStaticDir
function. Files will be served like so:
./public/css/style.css ->
http://example.com/css/style.css
Note: Jester will only serve files, that are readable by others
. On
Unix/Linux you can ensure this with chmod o+r ./public/css/style.css
.
Cookies
Cookies can be set using the setCookie
function.
get "/":
# Set a cookie "test:value" and make it expire in 5 days.
setCookie("test", @"value", daysForward(5))
They can then be accessed using the request.cookies
procedure which returns
a Table[string, string]
.
Request object
The request object holds all the information about the current request.
You can access it from a route using the request
variable. It is defined as:
Request* = ref object
params*: StringTableRef ## Parameters from the pattern, but also the
## query string.
matches*: array[MaxSubpatterns, string] ## Matches if this is a regex
## pattern.
body*: string ## Body of the request, only for POST.
## You're probably looking for ``formData``
## instead.
headers*: StringTableRef ## Headers received with the request.
## Retrieving these is case insensitive.
formData*: TMultiData ## Form data; only present for
## multipart/form-data
port*: int
host*: string
appName*: string ## This is set by the user in ``run``, it is
## overriden by the "SCRIPT_NAME" scgi
## parameter.
pathInfo*: string ## This is ``.path`` without ``.appName``.
secure*: bool
path*: string ## Path of request.
query*: string ## Query string of request.
cookies*: StringTableRef ## Cookies from the browser.
ip*: string ## IP address of the requesting client.
reqMeth*: TReqMeth ## Request method, eg. HttpGet, HttpPost
settings*: PSettings
Examples
Custom router
A custom router allows running your own initialization code and pass dynamic settings to Jester before starting the async loop.
import asyncdispatch, jester, os, strutils
router myrouter:
get "/":
resp "It's alive!"
proc main() =
let port = paramStr(1).parseInt().Port
let settings = newSettings(port=port)
var jester = initJester(myrouter, settings=settings)
jester.serve()
when isMainModule:
main()
Github service hooks
The code for this is pretty similar to the code for Sinatra given here: http://help.github.com/post-receive-hooks/
import jester, json
routes:
post "/":
var push = parseJson(@"payload")
resp "I got some JSON: " & $push