These are some of my thoughts and recommendations on using Nim in 2021 for web development. If you want a summary of what I think you should use, skip to the end. As this is my first post on the topic, I’ll be including a bit of history. My intent is not to compare to any specific libraries in other languages. While I’ve used others, it’s been a while and I don’t want to spend a ton of time researching and relearning them for the sake of a Nim related article.

What is Nim?

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula.

That’s what the website says, and I’ve found it to be pretty accurate in the last four years that I’ve used it.

Let’s dig more into web development in Nim. Unlike other languages, there are a few things that set Nim apart for web development.

  1. Nim has native support for compiling to C and JS (without using Webassembly) so it can be used to write Javascript code similar to how Typescript can be used to write Javascript
  2. Nim has meta-programming features that include templates and macros.
  3. Nim has a small community and fewer libraries in general.

Using Nim for front-end development

Since Nim can natively output JS, we can use it everywhere we want to use JS. The main library that is used to create Single Page Applications (or SPAs) in Nim is Karax. It uses a virtual DOM similar to React.

Karax can be thought of as an alternative to Vue/React/Angular. It is written in pure Nim, and has the benefits of a compiled, statically typed language - namely type checking and compile time optimization. Another feature of Karax is the DSL, or domain specific language. It can be used to generate HTML within Nim on the server (when compiled to C) or the client (when compiled to JS)

Here’s an example of just using the Karax DSL to generate HTML, without the bit that does the virtual DOM.

import karax / [karaxdsl, vdom]

const places = @["boston", "cleveland", "los angeles", "new orleans"]

proc render*(): VNode =
  buildHtml(tdiv(class = "mt-3")):
    h1: text "My Web Page"
    p: text "Hello world"
    ul:
      for place in places:
        li: text place
    dl:
      dt: text "Can I use Karax for client side single page apps?"
      dd: text "Yes"
      dt: text "Can I use Karax for server side HTML rendering?"
      dd: text "Yes"

echo render()

If you want to see how to use this sort of syntax to create an SPA, check out the Karax project README and examples. There are also a few tutorials and example projects made by the community here.

Using Nim for backend development

There are two web frameworks that I would recommend: Jester and Prologue. There are quite a few others, but most aren’t as mature or well tested as these two. In case you are curious about performance metrics between the two, you can look at these benchmarks.

Some History

Note that you may have heard of httpbeast, httpx, and httpleast in the Nim community. The difference between these is a bit confusing, so I’ll try to explain it here.

httpbeast was created by dom96 with the intent of getting Nim to perform really well as a web server. As such, it is dedicated to serving HTTP 1.1 requests as quickly as possible and doesn’t really do anything else. Jester then adds a layer on top of httpbeast to handle routing, cookies, and other niceties you’d expect from a “web framework”.

httpbeast doesn’t really support Windows due to parts its reliance on epoll/kqueue, so xflywind forked it into httpx to add Windows support using IOCP. httpx powers Prologue, the other web framework I mentioned.

httpleast is unrelated to any of these projects, and aims to serve HTTP requests as fast as possible using CPS. As of 2021, it’s still alpha level software and there are no sizeable Nim web frameworks using it to serve requests.

relation

Jester

import jester

routes:
  get "/":
    resp "<h1>Hello World!</h1>"

Jester is pretty bare-bones, but as it is written by a core Nim developer it is kept up to date and supports most things a “micro” web framework would support. Since it is minimal, it leaves it to the programmer to decide what to use to parse JSON, validate requests, and do templating. Since Jester is so minimal the only documentation is provided in a README file, and it is assumed that the developer has done web development before. The core features it has are routing, form handling, cookie handling, and serving static files.

Prologue

import prologue

proc hello*(ctx: Context) {.async.} =
  resp "<h1>Hello World!</h1>"

let app = newApp()
app.addRoute("/", hello)
app.run()

Prologue on the other hand, is a bit more full featured and has much more comprehensive documentation. It also has official support for websockets, openapi, middleware, and more. There are a couple of official middleware plugins such as auth, cors, clickjacking, and csrf protection.

Comparison

Personally, I tend to use Jester. It’s been around longer, and there are a few more third party libraries that add support for it because of that. For example, there is a simple websockets library and an authentication library that ship with support for Jester.

However, Prologue has many more features. If you were to compare the two in Python-land, I’d say that Jester is closer to Flask and Prologue is closer to Django in terms of the size and functionality of the two. Both are still very good frameworks that have a lot to offer.

  Jester Prologue
Created 2012 2020
Middleware No Yes
Auth Yes 1 Yes
Advanced Security (CSRF, Clickjacking) No Yes
Documentation Some Yes
Routing Yes Yes
Static Files Yes Yes
Cookies Yes Yes
Mocking No Yes
CORS Manual Yes
Websockets Yes 1 Yes
Uploads Yes Yes
Simple Yes No
Cross-platform Yes Yes

Templating

Templating is how we get our data to render as HTML. I’ve already discussed the Karax DSL, which is what I prefer as it was written by Araq (creator of Nim). However, it doesn’t really look like HTML so it is non-trivial to just paste in a snippet from Bootstrap and have it work. There is a package that can convert from HTML directly to the Karax DSL, but it is a bit clunky to use.

There are a few good alternatives in this space, if you prefer something else. A newer, more active project is Nimja, which has jinja2 style templates.

{% block content %}
  {# A random loop to show off. #}
  <h1>Random links</h1>
  {% const links = [
    (title: "google", target: "https://google.de"),
    (title: "fefe", target: "https://blog.fefe.de")]
  %}
  {% for (ii, item) in links.pairs() %}
    {{ii}} <a href="{{item.target}}">This is a link to: {{item.title}}</a><br>
  {% endfor %}

  <h1>Members</h1>
    {# `users` was a param to the `renderIndex` proc #}
    {% for (idx, user) in users.pairs %}
        <a href="/users/{{idx}}">{% importnwt "./partials/_user.nwt" %}</a><br>
    {% endfor %}
{% endblock %}

Nim also has libraries for working with Mustache templates, but those can be found on Nimble without too much hassle.

Databases/ORMs

Nim ships with support for SQLite, MySQL, and PostgreSQL in the standard library. Of course, these libraries are at a lower level - you need to manually write and form your SQL queries. Most language are pretty similar in this regard.

So, what I suggest to use for storing data is Norm, an ORM that lets you define your schema (as a Nim object), and then generate SQL based on it. It supports both SQLite and PostgreSQL, which should be sufficient for most projects.

import norm / [model, sqlite]
type
  User* = ref object of Model # define our schema
    email*: string
let dbConn* = open(":memory:", "", "", "") # open a SQLite memory DB
dbConn.createTables(User()) # create the table if it doesn't exist
var userFoo = newUser("foo@foo.foo")
dbConn.insert(userFoo) # create and insert a row

In case Norm is too high level or different from libraries that you’ve used in the past, I can also recommend allographer. It helps in forming SQL queries in Nim, so it isn’t quite as high level as an ORM, but it is much better than manually writing queries.

proc main(){.async.} =
  let result = await rdb
                    .table("users")
                    .select("id", "email", "name")
                    .limit(5)
                    .offset(10)
                    .get()
  echo result

Note that allographer relies more on manually specifiying a schema in SQL terms rather than specifying a Nim type.

Nim does have drivers for MongoDB and other non-relational database, but I personally haven’t used any, which makes it difficult for me to recommend any libraries. What I will say is that Nim has very nice interop with Python through nimpy, if you’re willing to lose the speed benefits in exchange for a massive ecosystem. Nim also has very good interop with C libraries, though I wouldn’t recommend writing your own wrappers if you are new to the language.

Conclusion

Nim has a strong enough web ecosystem to build web applications. The main issues today are documentation, and finding these libraries in the first place.

As far as my preferred stack goes, as you might expect, I use Jester for the backend, Karax’s DSL for templating, and Norm for persistence/storage. I recommend treeform’s websockets library (mentioned earlier) for websockets with Jester, and Federico’s httpauth library (also mentioned earlier) for handling authentication in a secure way.

However, I don’t feel comfortable recommending Nim for front-end web development, specifically Karax. There are too many rough edges and bugs for it to be ready for a production grade web service. Karax is also less performant than React/Vue due to it’s nature - there’s a layer of abstraction to access JS APIs, which inherently makes it slower unless Nim’s JS output is improved significantly. 2

Instead, what I recommend is using something like HTMX and hyperscript to add interactivity to pages, rather than reaching for an SPA framework. With something like React/Karax, you have four states to keep track of:

  1. Backend state/database
  2. Frontend state/application
  3. Virtual DOM state
  4. Actual DOM state

With something like HTMX/hyperscript, you just have two: backend state, and the DOM state. This is inherently unscalable - you won’t be able to build the next Google Docs with this stack without ripping your hair out. However, it works fine for 90% of web applications.

Anyway, that’s just my opinion. If you are seriously considering Nim for web development, I’d suggest trying out a few combinations of the above to see what it feels like on a tiny project. Pick your favorite and use that!

If I did miss something here, just let me know via Discord (I’m in the Nim #webdev channel).

  1. Support with an external library  2

  2. Nim’s JS output is suitable for replacing JS however. You could use it write Javascript libraries, without a problem. See nodejs for doing this more easily.