envelopmenuskypeburger-menulink-externalfacebooktwitterlinkedin2crossgithub-minilinkedin-minitwitter-miniarrow_rightarrow_leftphonegithubphone-receiverstack-overflow

Reason - part 1 - server

Whenever Facebook introduces something for developers it is very often a big thing. React, GraphQL, Flow and others - they changed the way of thinking of creating and building web apps. That's why when I first heard about Reason I was very intrigued.

What is the Reason?

In one line it's OCaml for humans with JSX support :) That's right - it's not a new language build completely from scratch but only a new syntax parser that translates to OCaml's one thanks to it's modular compiler toolchain. Problems with missing libraries or slow compiler or insufficient optimizer don't exist here as OCaml is a very mature ecosystem improved over the years.

The most exciting thing for me in Reason is ability to generate native binaries (no vm overhead, one binary, easy deploying) and ability to generate applications transpiled to javascript. Not a big thing, you'll say - many other languages have transpilers to JS. Correct, but none of them (except Typescript) supports JSX constructions as an integral part of the language. If you're a fan of React you know how important that is.

The second significant thing is that Reason has very similar syntax to JS and as a language is quite simple (not feature overwhelming) and powerful at the same time (very good type system, modules as first-class citizens).

Finally, I can have the same language for both server and the client. Thus, I can share application data structures because of that.

Showcase applicaiton

To see how it works in practise, I decided to create a simple client-server app. My goal was to produce a native binary on the server and js file on the client.

Reason comes with it's own build tool called Rebel. It supports multiple entry points and different output targets so it was my first choice. This is how configuration for Rebel can look in package.json:

"rebel": {
    "ocamlfindDependencies": [
        "core"
    ],
    "targets": [{
        "target": "web",
        "engine": "bucklescript",
        "entry": "src/web/index.re"
    }, {
        "target": "server",
        "engine": "native",
        "entry": "src/server/server.re",
        "unstable_flags": {
            "link": "-thread",
            "compile": "-thread"
        }
    }]
}

You can install OCaml and Reason using opam (OCaml package manager) or through npm and reason-cli. I wanted to install everything (including rebel) locally through package.json but many packages of @opam-alpha is changing rapidly and from time to time I got compilation errors so I gave up. I also had troubles with compiling cohttp which I needed for building native http server. I hope this npm ecosystem will stabilise in the near future because is seems to be a very convenient way of building reason apps. Finally, I had to abandon Rebel (there is also rebuild tool but since I wasn't able to install cohttp I resigned from it too) and I used bucklescript (OCaml transpiler to JS) to build a client part (with react) and a server part (with nodejs/expressjs).

The goal of the test application is to serve companies and their employees. One should be able to add new companies, add employees and look for the data like employee name or salary and so on.

I wanted to use the same data structures both on server and the client so I started with declaring data model that consist of an Employee:

type employee = {
    firstName: string,
    lastName: string,
    birthday: string,
    salary: float
};

and a Company:

type company = {
    name: string,
    employees: list employee
};

Server should be able to send those structures (records in Reason to be exact) as a JSON to the client and the client should be able to convert them back to the native form. That way I could operate on strongly typed data and reduce runtime errors to minimum.

What I need on the server is some real data. For now it will be hardcoded into the server:

let companies: ref list company = ref [
  { name: "SoftwareMill", employees: [
    { firstName: "Daniel", lastName: "Kos", birthday: "1979-08-16", salary: 1000.0 },
    { firstName: "Zenon", lastName: "Gamonski", birthday: "1943-06-16", salary: 1200.0 }        
  ] },
  { name: "UnsafeCode", employees: [
    { firstName: "Jack", lastName: "Strong", birthday: "1987-01-15", salary: 5000.0 },
    { firstName: "Jack", lastName: "Weak", birthday: "1983-05-10", salary: 6000.0 }        
  ] }
];

Please notice how easily is to initialise complex data structures. No types ceremony at all, like we have coded that in some dynamic language. ref (reference type) is needed to be able to reassign companies with a new list.

To deliver JSON output I provided two functions:

let employeeToJson = fun (e: employee) => {
    let json = Js_dict.empty();
    Js_dict.set json "firstName" (Js_json.string e.firstName);
    Js_dict.set json "lastName" (Js_json.string e.lastName);
    Js_dict.set json "birthday" (Js_json.string e.birthday);
    Js_dict.set json "salary" (Js_json.number e.salary);
    json;
};

and

let companyToJson = fun (c: company) => {
    let jsonEmployees = Array.of_list c.employees 
        |> Array.map (fun e =>; Js_json.object_ (employeeToJson e));
    let json = Js_dict.empty();
    Js_dict.set json "name" (Js_json.string c.name);
    Js_dict.set json "employees" (Js_json.array jsonEmployees);
    json;
};

Js_dict is a bucklescript's key-value dictionary abstraction over native JavaScript objects (remember that this server will be javascript file in the end). There is no (at this moment) any magic function like encodeJson/decodeJson that would translate javascript object to/from reason record without that boilerplate.

Having that I can finally send companies to the client:

App.get app path::"/companies" @@ Middleware.from (fun req res _ => {
    let jsonCompanies = Array.of_list companies |> Array.map (fun c => Js_json.object_ (companyToJson c));
    Response_.setHeader res "Content-Type" "application/json";
    Response_.setHeader res "Access-Control-Allow-Origin" "*";
    Response_.setHeader res "Access-Control-Allow-Methods" "GET, POST, OPTIONS, PUT, PATCH, DELETE";
    Response_.setHeader res "Access-Control-Allow-Headers" "X-Requested-With,content-type";
    Response_.sendJson res (Js_json.array jsonCompanies);
});

Unfortunately, original Response declaration doesn't contain setHeader method and I needed to provide it on my own simply by declaring Response_ module:

module Response_ = {
    include Response;
    external setHeader: t => string => string => unit = "" [@@bs.send];
};

[@@bs.send] is a bucklescript's "thing" that sends message to the javascript's object (you can read more about it here). include will simply mixin Response definition into Response_ module.

When you access http://localhost:3000/companies you should get:

[
  {
    "name":"SoftwareMill",
    "employees":[
      {"firstName":"Daniel","lastName":"Kos","birtday":"1979-08-16","salary":1000},
      {"firstName":"Zenon","lastName":"Gamonski","birtday":"1943-06-16","salary":1200}
    ]
  }, {
    "name":"UnsafeCode",
    "employees":[
      {"firstName":"Jack","lastName":"Strong","birtday":"1987-01-15","salary":5000},
      {"firstName":"Jack","lastName":"Weak","birtday":"1983-05-10","salary":6000}
    ]
  }
]

It seems to be working. Good. Now, I need to add some filtering capabilities to be able to search for company by name or any property of employee that belongs to that particular company.

App.get app path::"/companies" @@ Middleware.from (fun req res _ => {
    let name = getDictString (Request.query req) "name";
    let firstName = getDictString (Request.query req) "firstName";
    let lastName = getDictString (Request.query req) "lastName";
    let birthday = getDictString (Request.query req) "birthday";
    let salary = getDictNumber (Request.query req) "salary";

    let hasEmp = fun (emps: list employee, p: employee => bool) => 
        List.length emps == 0 || List.exists p emps;

    let jsonCompanies = !companies
        |> List.filter (fun c => c.name == getOpt(name, c.name))
        |> List.filter (fun c => hasEmp (c.employees, fun e => e.firstName == getOpt(firstName, e.firstName)))
        |> List.filter (fun c => hasEmp (c.employees, fun e => e.lastName == getOpt(lastName, e.lastName)))
        |> List.filter (fun c => hasEmp (c.employees, fun e => e.birthday == getOpt(birthday, e.birthday)))
        |> List.filter (fun c => hasEmp (c.employees, fun e => e.salary == getOpt(salary, e.salary)))
        |> Array.of_list
        |> Array.map (fun c => Js_json.object_ (companyToJson c));

    setRespHeaders res;
    Response_.sendJson res (Js_json.array jsonCompanies);
});

Functions in functions, lambdas defined with far arrows - familiar for programmers from other popular languages. Should be easy to follow. |> is the reverse application function that makes successive calls a little bit cleaner. Exclamation mark before companies variable is a way to access reference value. All those chained filters try to match companies with a given name or with a given employee.

getDictString is a helper function that extracts given key from JSON object and returns optional string. option is nothing more than a generic variant type:

type option 'a = None | Some 'a.

getOpt simply extracts value if it exists or returns provided default value.

The last missing part is adding data. For a company, this is nothing more than concatenating existing companies list and a new one:

App.post app path::"/companies" @@ Middleware.from (fun req res _ => {
  let name = getDictString (Request.params req) "name";
  let defaultName = "Company " ^ string_of_int (List.length !companies);
  companies := [{name: getOpt(name, defaultName), employees: []}] @ !companies;
  Response.sendJson res (makeSuccessJson())
});

Concatenating can be done with an @ function or with List.append. Adding a new employee is a little bit more complex as company has to be found before appending employee to the existing employee list:

exception CompanyNotFound;
exception EmployeeFieldMissig;

App.post app path::"/employees" @@ Middleware.from (fun req res _ => {
    let reqData = Request.asJsonObject req;
    let body = getDictObject reqData "body";
    let getBody = fun key => switch (body) {
    | Some json => getDictString json key
    | _ => None
    };
    try {
        let name = getOptExc(getBody("name"), CompanyNotFound);
        let firstName = getOptExc(getBody("firstName"), EmployeeFieldMissig);
        let lastName = getOptExc(getBody("lastName"), EmployeeFieldMissig);
        let birthday = getOptExc(getBody("birthday"), EmployeeFieldMissig);
        let salary = getOpt(getBody("salary"), "0.0");

        let company = List.find (fun c => c.name == name) !companies;
        companies := List.map(fun c => c.name == name ? {
            name: company.name,
            employees: company.employees @ [{
              firstName: firstName,
              lastName: lastName,
              birthday: birthday,
              salary: float_of_string salary
            }]
        } : c) !companies;
        Response.sendJson res (makeSuccessJson());
    } {
      | CompanyNotFound => Response.sendJson res (makeFailureJson "Company name was not found")
      | EmployeeFieldMissig => Response.sendJson res (makeFailureJson "One of required employee fields is missing")
    }
});

I defined two possible exceptions that may occur here. First when company cannot be found and the second when one of employee fields is missing. In reason similarly to for example javascript block of code can be put inside a try command with exception list at the end. One additional thing that is needed to get json data from post body request is adding body parser:

[%%bs.raw{|
var bodyParser = require('body-parser');
app.use(bodyParser.json());
|}];

Unfortunately I had to add it through bucklescript's raw instruction that allows to inject any javascript code.

That simple server functionality should be enough to build a frontend client for now. Reason turned out to be quite easy to start working with, without reading tons of documentation in the first place. The most difficult was initial project setup and getting familiar with OCaml's tooling. Also documentation for bucklescript was a little bit problematic as it uses OCaml syntax everywhere. Official Reason documentation is still a work in progress so one has to be prepared for following Discord's channels and Github's discussions.

For someone like me, who never wrote any line of code in OCaml but wrote many lines in C family languages, Reason seems to be a very interesting option. I don't have to get used to completely different syntax and I don't have to rich for dynamic languages to get compact code. Finally, I have statically typed language without unnecessary complexity (like in Scala for example). I didn't cover many interesting features of the language in the server but it doesn't make sense to put them without a reason ;). Let's wait for the next part where I'll try to connect server with a React client.

Update

Recently, appeared a new library to encode/decode JSON for bucklescript called bs-json. I will try to use it and describe it in the next blogpost.