React App Embedded in Chicken Program

Written by Dominik Pantůček on 2025-05-22

reactchickenschemejavascript

Embedded platforms are ubiquitous, however they usually have limited resources. However a web interface is often useful for these systems. In this article we explore the possibility of embedding React app in statically-linked CHICKEN binary that can be deployed to such systems by simply copying it where it can be run as a system service.


Programming embedded applications has usually been performed in assembly language or low-level programming languages like C. It is possible to use Scheme as a high-level alternative while still compiling the result to machine code which can be run on the target syste as-is. In addition, CHICKEN Scheme supports statically linking most of the components used by such service.

Modern web interfaces can be created by the React framework without too much hassle. After the decline of create-react-app, other tools are available. For example the Vite framework. Creating a new React application in Vite is as simple as:

npm create vite@latest

We opt for the project name "frontend", type "React" and plain " Javascript" platform. The result can then be compiled into a bunch of HTML, JS and CSS file as follows:

cd frontend
npm install
npm run build

Building the application creates the static files in frontend/dist/ directory. The first task will be to pack all the files contened therein into a single scheme module. We create a simple tool for this: schemify-tree.scm script.

This program just traverses the dist directory tree and output Scheme source that contains an associative list of keys (the relative paths of the files) and strings (the contents of the files) as values. The generated module exports a single procedure that can be used for looking up the contents of any given key which optional default fallback value. This will simplify integrating the static data into the webserver implementation.

A typical React dist directory contains files like the following:

assets/index-rkgPUzqi.css
assets/index-CYw0FOc4.js
vite.svg
index.html

And the corresponding module is generated as follows:

(module frontend (frontend-lookup)
  (import scheme (chicken base))
  (define alst
    '(("assets/index-rkgPUzqi.css" . "@charset \"UTF-8\";:root{font-fami...")
      ("assets/index-CYw0FOc4.js" . "(function(){const t=doc...")
      ("vite.svg" . "<svg xmlns=\"http://www.w3.org/2000/svg\" xml...")
      ("index.html" . "<!doctype html>\n<html lang=\"en\">...")))
  (define (frontend-lookup key .vs)
    (let ((klst (assoc key alst)))
      (if klst
          (cdr klst)
          (if (null? vs)
              (error 'frontend-lookup "key not found" key)
              (car vs))))))

For the web-server implementation the spiffy egg can be used. The usage is rather simple:

(import spiffy (chicken tcp))
(define listener (tcp-listen 8080))
(accept-loop listener)

As no files are being served, all requests end up in the not-found handler. In the handler it is then possible to distinguish between static assets and API calls:

(handle-not-found
 (lambda (path)
   (define upath
     (string-intersperse
      (map ->string (cdr (uri-path (request-uri (current-request)))))
      "/"))
   (cond ((equal? upath "")
          (send-response #:body (frontend-lookup "index.html")))
         (else
          (let ((maybe-asset (frontend-lookup upath #f)))
            (cond (maybe-asset
                   (send-response
                    #:headers (let ((ext (car (reverse (string-split upath ".")))))
                                (cond ((equal? ext "css")
                                       '((content-type #("text/css" ()))))
                                      ((equal? ext "js")
                                       '((content-type #("text/javascript" ()))))
                                      (else
                                       '())))
                    #:body maybe-asset))
                  (else
                   (when (not (handle-api-calls))
                     (send-response #:body (frontend-lookup "index.html"))))))))))

As we can see the handler conforms to the React specification which requires the web-server to return the contents of its index.html file whenever there is no other handler. The handler uses the frontend-lookup from the module generated by the schemify-tree tool for serving the static assets. Then a new method - handle-api-calls - has to be implemented where the API handling logic will be is created.

For dispatching the API requests, each servlet is wrapped in a lambda that checks whether it matches the given URI path and if it does, it binds requested path components as local bindings. Upon a successfull match it returns #t and if this particular servlet didn't match the path a #f value is returned. That allows a very simple dispatching logic:

(define api-servlets
  (list account-barcode-info
        ...))

(define (api-dispatch plst)
  (let loop ((as api-servlets))
    (if (null? as)
        #f
        (if ((car as) plst)
            #t
            (loop (cdr as))))))

And how the servlets are created? With a bit of define-syntax and syntax-rules a small DSL just for that can be created:

(define-syntax try-match-lambda1
  (syntax-rules ()
    ((_ pp plst ((arg ->conv) . args) body)
     (let ((as (->conv pp)))
       (cond (as
              (let ((arg as))
                (try-match-lambda0 plst args body)))
             (else
              #f))))
    ((_ pp plst (lit . args) body)
     (if (equal? pp lit)
         (try-match-lambda0 plst args body)
         #f))))

(define-syntax try-match-lambda0
  (syntax-rules ()
    ((_ plst () (expr ...))
     (let ()
       expr ...
       #t))
    ((_ plst (arg . args) body)
     (cond ((null? plst)
            #f)
           (else
            (let ((pp (car plst)))
              (try-match-lambda1 pp (cdr plst) (arg . args) body)))))))

(define-syntax try-match-lambda
  (syntax-rules ()
    ((_ args . body)
     (lambda (plst)
       (try-match-lambda0 plst args body)))))

(define-syntax define-try-match
  (syntax-rules ()
    ((_ (name . args) . body)
     (define name (try-match-lambda args . body)))))

(define-try-match (account-barcode-info "barcode" (barcode identity))
  (send-response
   #:body (alist->json-string
           `((amount . ,(bd-barcode-lookup barcode))))))

As we can see, it is really easy and the result can be directly used from within the React app using the fetch API. And even though it looks rather complex, everything can now be compiled into a single binary using CHICKEN's csc command.

For all the details, have a look at the sources at git.brmlab.cz. All the nitty-gritty compilation and linking details can be inferred from the backend/Makefile source.

All in all if you need a web interface for an embedded system, CHICKEN Scheme and React.JS are a good choice. See ya next time!