You might be wondering, can ReasonML really be used on the backend side without transpiling to JavaScript as a native solution? Might this be a new way to write server code? Let’s find out!
In this tutorial, we will write a simple native GraphQL server with Reason and OCaml libraries without using JavaScript at all.
The end result will give us high performance, type safety out of box, nice ReasonML syntax, and we will also be able to use the whole OCaml ecosystem. The easiest way to do this is to use a Bucklescript for transpiling ReasonML to JavaScript, and then run it in a NodeJS server. But that's not what we are going to do here, as this solution is not native and needs to run in NodeJS runtime.
LET'S DIVE INTO REASON/OCAML!
Before we can start playing with ReasonML we need to install a toolchain to build our project.
First of all, we need to install OPAM, which is something like npm in the NodeJS world. With OPAM, we can install and update our packages. OPAM can be installed globally with a "brew install gpatch" or a "brew install opam," or you can follow a more detailed installation guide here.
Let’s check our installation with “opam --version”. You should see something like "2.0.1"
Following a successful installation, we can start creating our new project:
mkdir graphq-reasonml-blog
cd graphq-reasonml-blog
Because we will be using ReasonML, we also need to install it:
opam install reason
For project managing, we will use Dune (https://github.com/ocaml/dune), which will also need to be globally installed:
opam install dune
eval $(opam env)
Dune will manage your project with Dune files. A Dune file is something like package.json. The main difference is that instead of JSON syntax we are using Lips-like syntax.
We will create the following Dune file:
touch dune
With this
(executable
(name main),
we are describing the input point of our application. And with
(libraries cohttp lwt graphql-lwt yojson)
we are describing packages that we will be using for our application.
Once we’ve completed the Dune settings, we can install our required libraries with
(executable
(name main)
(libraries cohttp lwt graphql-lwt yojson))
Next, we need to create a main.re file where we can put our ReasonML code, something like index.js in the NodeJS world.
As for which GraphQL library will be best here, we will be using ocaml-graphql-server, which is a powerful option for a GraphQL server in the OCaml world.
We will try to run a GraphQL example from OCaml GitHub.
As this is an OCaml package, we need to convert Ocaml to ReasonML. You can do this by hand, using a Chrome extension, or you can use the refmt tool for converting OCaml syntax to ReasonML with the command ‘refmt graphql.ml’
For simplicity, I’ve made a simple version of a GraphQL server. If you want, you can go with this, or you can try the complex one in the ocam-graphql repository with ‘subscriptions’ and more.
(If you are unsure about Reason’s syntax, you can check it out here or watch a great video course here.
First of all, we will need to define our types. Let’s go with a simple one and create a type user with two attributes, id and name, and create some hardcoded values for them. The great thing about ReasonML is that you can define these types for your GraphQL server, and then share it with the client:
type user = {
id: int,
name: string,
};
let alice: user = {id: 1, name: "Alice" }
let bob: user = {id: 2, name: "Bob" };
let users: list(user) = [alice, bob];
With this type, we need to define our GraphQL schema. The syntax should be pretty straightforward. We can create a new Schema from the Graphql_lwt module and define what fields our GraphQL schema has and how it should resolve these fields.
Right now, we will simply return sent values:
let user =
Graphql_lwt.Schema.(
obj("user", ~fields= _ =>
[
field("id", ~args=[], ~typ=non_null(int), ~resolve=(_, p) =>
p.id
),
field("name", ~args=[], ~typ=non_null(string), ~resolve=(_, p) =>
p.name
)
]
)
);
With our user schema, we are ready to create a main schema for our GraphQL server, which will have one query — users — and will return an array of our users.
let schema =
Graphql_lwt.Schema.(
schema(
[
io_field(
"users",
~args=[],
~typ=non_null(list(non_null(user))),
~resolve=((), ()) =>
Lwt_result.return(users)
),
],
)
);
With all this ready, we can start our first GraphQL server written in native ReasonML!
let () = Graphql_lwt.Server.start(~ctx=_req => (), schema) |> Lwt_main.run;
You can review the whole project example here.
Once we have our ReasonML code ready, we can build it and run it. (If you are using a Mac OS X, don't be scared of the .exe extension; it has nothing to do with Windows, it just means executable.)
This will build your project into a main.exe executable file:
dune build main.exe
And this will run your GraphQL server:
dune exec ./main.exe, or ./_build/default/main.exe
After running this, our GraphQL server should be up and running on the port 8080. Now, let’s go to http://localhost:8080/graphql to visit our GraphQL interface:
That’s it! You have successfully build your first GraphQL server with ReasonML and OCaml!
You can now start exploring all others OCaml libraries and try using them with ReasonML in your projects.
Next time, we will focus on other cool stuff from the OCaml universe like MirageOS and connect our server to the real database.