Board — an experimental typesafe back-end micro-framework for Elm

Alexey Tukalo
6 min readJul 14, 2019

A bit more than a year ago, I started an exciting open-source project. The goal was to improve my understanding of backend development by the creation of a toy back-end micro-framework. The article is dedicated to the results of my work.

I selected Elm as a platform for my project due to a lack of existing back-end infrastructure and dynamic community, which I expected to attract by my creation. Unfortunately, I was not aware of the underlying change in internal policies, which led to a complete ban on any native code on third-party Elm packages. The fact caused a misunderstanding which didn’t let me get any collaboration from a core Elm development team. So, I decided to finish it anyway and let’s take a look at the outcome.

The text below is a shortened version of original documentation published as readme file of the project. It has a bit more details regarding the API and architecture of the project. Also, there are seed and demo projects to help you develop your application with the Board.

Motivation

Nowadays, almost every cloud platform offers possibilities for seamless deployment of Node.js applications. The goal of the project is to combine deployment capabilities of Node.js and safety of statically typed purely functional languages for rapid development of a small micro-service application.

The main reasons Elm was chosen over GHCJS or PureScript are a steeper learning curve, excellent documentation, active community and build-in state management system. It also has no way to be used on a back-end, so it was rather cool to be first.

Implementation

Board was partly inspired by Spock, Express and other Sinatra like frameworks. Typesafe URL parsing planned to be one of the main features. The parsing engine was moved to a separate project available for everybody at Elm package manager as Pathfinder.

Router

It is the fundamental part of an application, basically it is just a function which defines the process of turning Request object into Response one. Request object describes essential information about incoming inquiry.

Response is a representation of the server reply. The object is matched with a client by a request id. Board can create an initial response for a Request. Shared.getResponse function constructs an empty response with an id of the provided request record.

Routing combinators

A router function is composed out of several custom request handling functions and by the routing combinators. The combinators are represented by a function which takes a path description as a first argument and handler for the specified address as the second one. Pathfinder is utilized to describe the URL, which triggers the path handler. The handling function is responsible for turning the request record and params extracted by the URL parsers into one of the three possible results:

  • Redirection to a new path
  • Replying with an appropriate response record
  • Passing the request further, possible with attached cargo

use routing combinators are capable of handling any types of HTTPS request while get, post, put and delete are only working with correspondent HTTP methods.

Stateless and Stateful

State management is not a trivial task for a purely functional application. Board utilizes Elm’s architecture to provide handy tooling for accessing and modifying an application state.

There is a particular type of rout handlers capable of providing access to the state of the application. The access is granted by transactions. Instead of returning the AnswerValue record itself a route handler attached by such a routing combinator returns a transaction from a current state to tuple which composes state and AnswerValue. State-less combinators are not capable to access current state.

Sync and Async

On another hand, route handler can be represented by an atomic as well as an asynchronous operation. Atomic operations are usually related with some processing of request’s data, parsing or local state modifications. Async ones are usually related to file handling, database manipulations or communication with third-party services. Task handles the asynchronous nature of the actions.

Synchronous processing usually sequentiality handles the request and immediately return a correspondent response.

Async processing is usually caused by awaiting of an asynchronous action performed based on a handled request.

Initial router

Routing combinators are responsible for combining of an existing router with new path handler. So, therefore, a first router is needed. It is represented by any function which satisfies following signature Request String -> Mode String (Answer String State String). The function is going to be called once for every request. It might execute some parsing or authentication actions. Result of the actions can be propagated by a Cargo property of a Request record.

Node.js server

Board uses calls to native Node.js API to establish the HTTP/HTTPS connection. An incoming Request object is processed and converted into a Request record exposed to Elm code. The Response object is placed to a Map. The object is popped up from the Map based on a Response record created as output of Elm code. From time to time the Map is cleaned out of Responses which are older than the timeout.

File handling

File handling is implemented via a very simple library based on Node.js fs. Practically it contains functions to read, write and parse files. Files are represented by a higher-order function which takes a function from Node.js Buffer to arbitrary Elm type. The data itself is enclosed inside of closure so that it is not directly accessible at Elm side without proper handling. There are two standard parsers: string and dict. Also, there are functions specialized on the encoding of Elm types to File: fromString and fromDict. The last but not least there is getContentType function which returns content-type based on file name. The function powers static.

Known limitations

It was an experimental project which was mainly done to investigate the possibility of adapting Elm architecture for back-end development at the same time as improving my knowledge of Node.js APIs.

Due to the nature of Elm architecture and Node.js, the system in a current condition is not capable of handling multi-threaded application. Implementation of such a functionality is way beyond the scope of the project right now.

The project was started right before Elm 0.19 was released. The version of Elm dramatically changed the way native code is handled. Native code is entirely forbidden for third-party libraries since the release. Therefore the project didn’t get any support from the mainstream Elm community, and it will never be available at the package manager. Also, due to dramatic changes in the infrastructure of Elm, the 0.18 and older version might be eventually discontinued.

Since it is essentially a single person pet project, there is a significant lack of testing, especially the production one. I will personally be happy to see the project based on the library, but you have to be aware of risks.

The micro-framework currently supports only HTTP and HTTPS. Sockets are out of scope.

Some future development is required at following directions: an authentication, a cookies and a file handling.

Future plans

Due to recent changes in a platform, the project ended up to be just a proof of concept. Since it is not possible to update it for the newer version of Elm, it is also not possible to publish it in Elm package manager, because of policy regarding native modules which are an essential part of the system in this case.

PureScript seems to be the best option for migration of the project, but lack of the Elm Architecture would require reconsideration of the state management system. At the same time, it will open an opportunity to utilize and advantages of PureScript type system like Type Classes and Higher-kind types. It might be especially useful for implementation of the URL parsing eDSL.

Another viable opportunity is GHCJS, but in my point of view, it is overkill since there are many brilliant back-end frameworks for Haskell and there is no need to mess around with such a complication as a translation to JS and utilization of Node.js infrastructure.

--

--

Alexey Tukalo

Young language agnostic software developer interested in functional programming, software design, web development and computer graphics.