View on GitHub

Scheme-Experimentations

Could the beautiful scheme language be used to develop a database driven web application ?

Download this project as a .zip file Download this project as a tar.gz file

You love scheme

And so do we.

You understand that simplicity is beautiful, and that scheme is the very essence of what it means to write software. But somehow, scheme is perceived as an abstract ideal that can hardly be used in the real world. We begged to differ, so we challenged ourselves to build a credible web application written in our beloved language, and this project was born.

We hope you can find inspiration in it.

Handling HTTP requests

Many have explored the idea of building an HTTP server written in scheme. We instead chose to focus on writing our application logic in scheme, and to rely on the tools offered by Linux for everything else. So we ended up embedding the Chicken Scheme runtime into a FastCGI module hosted by the Apache HTTP server.

The FastCGI foreign interface provides access to the following low level functions:

FCGX_GetParam()
FCGX_GetLineEx()
FCGX_PutS()

The HTTP module then offers the following scheme procedures:

(http-request-method)
(http-read-fastcgi-stream)
(http-write-header)
(http-write-body)

The request processing code is macro generated, so all you have to code looks like this:

(define-http-binding
  "GET"
  "^customers/(\\d{1,6})$"
  get-customer-service
  http-parse-get-customer-request
  http-format-get-customer-response)

(define (http-parse-get-customer-request route-captures request-body)
  (make-get-customer-request
    (string->number (car route-captures))))

(define (http-format-get-customer-response response)
  (json-format-response response json-format-get-customer-response))

Then all is registered to the Apache HTTP server with this simple configuration:

ScriptAlias /api/ "/usr/local/apache2/api/"

<Directory "/usr/local/apache2/api">
  AllowOverride None
  Require all granted
  SetHandler fcgid-script
  Options +ExecCGI
  FcgidWrapper /usr/local/apache2/api/scheme virtual
</Directory>

Parsing and formatting JSON

We chose the ubiquitous JSON format to carry our HTTP payloads. The Jansson library fitted our needs nicely, so we built the Jansson foreign interface to provide access to the following low level functions:

json_loads()
json_integer_value()
json_object_set()
json_dumps()

The JSON module then offers the following scheme procedures:

(with-parsed-json-object)
(json-object-property)
(json-object-property-set!)
(json-object->string)

The parsing and formatting code for the JSON requests and responses is macro generated, so all you have to code looks like this:

(define-request new-customer-request
  (first-name string #t 1 50)
  (last-name string #t 1 50)
  (is-vip boolean))

And you automatically gain code capable of parsing HTTP requests such as:

POST /api/customers HTTP/1.1
Host: localhost

{
  "first-name": "Alice",
  "last-name": "Allison",
  "is-vip": true
}

Storing persistent data

We intended to fully exploit our fine-grained control over the HTTP requests, notably through aggressive HTTP caching. So we relaxed our performance requirements at the database level, and ended up leveraging the widely used SQLite library.

The SQLite foreign interface provides access to the following low level functions:

sqlite3_open()
sqlite3_prepare_v2()
sqlite3_bind_int()
sqlite3_step()

The SQL module then offers the following scheme procedures:

(with-sql-connection)
(within-sql-transaction)
(sql-execute)
(sql-read)

The code for the basic CRUD operations is macro generated, so all you have to code looks like this:

(define-table
  (customers-table
    "customers")
  (customer-row
    ("customer-id" integer)
    ("first-name" string)
    ("last-name" string)
    ("is-vip" boolean))
  (custom-selects)
  (custom-executes))

And you automatically gain the following procedures:

(make-customer-row)
(customers-table-insert)
(customers-table-select-by-customer-id)
(customers-table-update)
(customers-table-delete)

Spread the scheme love

As you can see, we took great care not impose any design decisions on anyone who would like to build upon this project. Simply knowing you used our foreign interfaces will make us smile. You liked our modules, or even reused some macros? We are happy like it's our birthday.

Please fork this project, set yourself up and build the next big thing in scheme.