Getting Started with Fable. NodeJS


Photo by Markus Winkler on Unsplash

Welcome back! This article is the part of “Getting started with Fable” series, so if you missed the previous articles please visit:

The idea of this series is to create a fully functional application that can be used as a starting point for any enterprise application. In particular, I’m looking into a full-stack application written in F# which transpiles into JavaScript / React / Redux application on the front-end and Node.js / Express on the back-end. I know that it sounds a bit weird to transpile F# to JavaScript on back-end instead of creating a .NET application.

As I’ve mentioned in the very first article in the series the main driver for this series is learning something new. So the technical stack for this project complies with two simple statements:

  • All technologies should be popular and should be used in enterprise development right now. The only exception to this rule is the F# itself, it’s not popular in enterprise development. But I see great potential in functional programming in the near future, so working with F# is more important than any other goal I set for myself. Although I hope that articles like these can prove that developers can create projects they create every day not only using JavaScript, TypeScript, C#, and Java. Don’t get me wrong there is nothing wrong with these languages, they are great and their popularity is just proving this. I’m just a bit tired of them, looking for something new.
  • All technologies should be new to me. I want to face as many new technologies as possible. It can sound a bit strange but I’ve never worked with React on production. For the last 3 years, I’m working with Angular only, and React is still a dark horse for me. From the other perspective, I’ve some experience with .NET and C#, hence I’m not able to use .NET as a back-end for this particular project. So I’ve decided to try to transpile F# to Node.js. More challenges!

There is one thing I want to share before we dive into the Node.js server setup. There is one interesting feature added recently to VS Code which in my opinion can change the way we work with environments and projects and I’m really excited to talk a bit about it.

Development containers

Recently VS Code introduced a feature called development containers. The main idea behind this feature is the creation of a Docker image for the workspace. It sounds simple, the idea to isolate projects was there for a very long time. Some of my colleagues were creating VirtualBox images for each project to keep their main OS clean and to be able to easily switch between projects, some developers reinstall OS after each project rotation, etc. But this feature simplifies and automates the process. Let’s take a look at the main advantages of using this approach:

  • This feature introduces the concept of front-end and back-end for your IDE. It means you can have all your UI configurations (fonts, themes, trackers, etc.) installed on the main instance of VS Code. On the other hand, the container will hold any project-specific dependencies (e.g. languages syntax highlighting). Let’s take a looks at a very simple example. Currently, I’m working with two projects: in one project I’m working with C#, TypeScript, and SCSS, in the second one I’m working with F#, React, CSS. It’s not really great to have all extensions for both projects installed simultaneously. And I’ve fixed this issue by creating a development container for my F# project. Now, these extensions are only starting to work when I’m opening a particular workspace. On the other hand my theme, hotkeys, and other UI-related extensions available all the time.
  • This features fixes issues when a developer is working with several projects that have incompatible dependencies. For example, you are supporting legacy Node.js 8 application and developing new Node.js 14 applications at the same time. It’s inconvenient to switch between versions of Node.js each time you need to fix a critical bug in the legacy application. Also, it can lead to bugs when you fixed a bug for one version then forgot to switch back and committed code tested in an incorrect environment. Containers solve this issue easily. When you are opening workspace VS Code will start the container associated with this workspace and you are safe.
  • And last but not least. I’m a huge fan of well-documented projects. When I’m joining a new project I don’t want to look for people who know how to set up the project and development environment. I want to open README.md and find answers to all questions. There are two main issues with this file: on most of the projects, it’s missing/almost empty, on all projects it’s obsolete. Development containers solve this issues, you don’t need documentation, you’ll have a dockerfile (a script) which will always be up-to-date because developers will update it as soon as configuration changes to update their container. The only steps you’ll write in your README.md are install VS Code, install Docker. And even if people are not using VS Code for whatever reason they can always open the dockerfile and execute steps from it on their machines. Brilliant!

With all that said let’s create a container configuration for our Fable project. There are a couple of simple steps you need to do in order to get the fully functional Docker container.

Install the Docker. This is quite obvious, you can use the manual from the official web site. Unfortunately, the script that they are providing for Ubuntu is not always working (depending on the Ubuntu version you are working with, also it’s not working for distros, which are based on Ubuntu):

sudo apt-get install
sudo apt-get install apt-transport-https
sudo apt-get install ca-certificates
sudo apt-get install curl
sudo apt-get install gnupg-agent
sudo apt-get install software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add –
sudo apt-key fingerprint 0EBFCD88
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
sudo groupadd docker
sudo usermod -aG docker $USER
view raw docker.sh hosted with ❤ by GitHub
docker.sh

Then you need to need to install ms-vscode-remote.vscode-remote-extensionpack extension pack to your VS Code. After that, you’ll be able to execute the command Add Development Container (select F#/.NET Core):

Remote-Container Commands Menu

The default configuration will almost meet our needs, the only little issue we need to solve is to install Node.js and Yarn to this container. You can find the full version of Dockerfile below (the section which I’ve added is marked with appropriate comment):

#————————————————————————————————————-
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#————————————————————————————————————-
FROM mcr.microsoft.com/dotnet/core/sdk:3.1
# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser"
# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs
# will be updated to match your local UID/GID (when using the dockerFile property).
# See https://aka.ms/vscode-remote/containers/non-root-user for details.
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
# Configure apt and install packages
RUN apt-get update \
&& apt-get -y install –no-install-recommends apt-utils dialog 2>&1 \
#
# Verify git, process tools, lsb-release (common in install instructions for CLIs) installed
&& apt-get -y install git openssh-client less iproute2 procps lsb-release \
#
# Install Node.js and Yarn
&& curl -sL https://deb.nodesource.com/setup_12.x | bash – \
&& apt-get install -y nodejs \
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add – \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt update && apt install yarn \
#
# Create a non-root user to use if preferred – see https://aka.ms/vscode-remote/containers/non-root-user.
&& groupadd –gid $USER_GID $USERNAME \
&& useradd -s /bin/bash –uid $USER_UID –gid $USER_GID -m $USERNAME \
# [Optional] Add sudo support for the non-root user
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\
&& chmod 0440 /etc/sudoers.d/$USERNAME \
#
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=dialog
view raw Dockerfile hosted with ❤ by GitHub
Dockerfile

The last step is to modify the devcontainer.json. As far as our instance of VS Code becomes a UI only we need to install Ionide to the container itself. Also, we need to forward ports for our server and client applications (in my case 3000 for server and 4200 for a client):

{
"name": "Fable",
"dockerFile": "Dockerfile",
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"FSharp.useSdkScripts": true
},
"appPort": [
4200,
3000
],
"extensions": [
"Ionide.Ionide-fsharp"
]
}
view raw devcontainer.json hosted with ❤ by GitHub
devcontainer.json

Now you need to reopen your working folder in a container (VS Code should offer you to do so) and you are good to go.

fantomas

One more amazing thing I want to share with you is Fantomas. This is a code formatting tool. I love such tools because they make code written by different team members look similar. But the amazing thing about Fantomas is that it’s already integrated into the Ionide extension for VS Code. The only thing you need to do is to create fantomas-config.json. In my case I left it almost empty:

{
    "$schema": "http://json.schemastore.org/fantomas"
}

Now you can format F# files via hotkeys or by enabling the “format on save” option. Amazing!

Node.js

Now our task to create a simple Node.js web server using Express. As a first step we need to create a new project, let’s call it api. This project will have only two files and one dependency Fable.Core, at least for now. The first file will describe bindings for working with Express, the second one will be our server entry point. You can find the project file definition below:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="bindings/express.fs" />
<Compile Include="main.fs" />
</ItemGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Core" Version="3.1.5" />
</ItemGroup>
</Project>
view raw api.fsproject hosted with ❤ by GitHub
api.fsproj

Now let’s move to the implementation part. At first, we will create bindings for the Express server. I was trying to use existing bindings from Fable.Node package, but I did not manage to get it working. Maybe because I was doing something wrong, maybe because it’s not updated very frequently – not sure. Again, writing bindings is not rocket science and you add new functions as you use them so currently, I don’t see a huge problem here, at least at this scale of the application.

module Express
open Fable.Core
type Response =
abstract send: string -> unit
type Application =
abstract get: string -> (obj -> Response -> unit) -> unit
abstract listen: int -> (unit -> unit) -> unit
[<ImportDefault("express")>]
let inline Express (): Application = jsNative
view raw express.fs hosted with ❤ by GitHub
express.fs

For our prototype to work we only need to describe two types: Application and Response. I’ve used different syntax for importing Express code (not sure why but importDefault didn’t work for Node.js, will investigate it further). It is important to note that unlike the front-end ecosystem which is using import syntax for referencing a dependency Node.js is using an old model called CommonJS. It means when we Fable transpiles code from F# to JavaScript it should use the require function. Further down you’ll see that it’s mandatory to pass the additional parameter “--commonJS” to produce valid Node.js code. Other than that everything is pretty standard.

Now let’s write a simple “Hello World!” Express application. It’s really basic, the only thing it does it prints “Hello World” at the server start and provide users endpoint which returns a hardcoded string. In our code the port of the server is also hardcoded as 3000:

module Main
open Express
let application = Express()
application.get "/users" (fun _req res> res.send "Users")
application.listen 3000 (fun () -> printf "Hello World")
view raw main.fs hosted with ❤ by GitHub
main.fs

Now we need to add a couple of packages as dependencies:

npm i --save-dev @babel/plugin-transform-modules-commonjs
npm i --save-dev fable-splitter

The first one is responsible for creating CommonJS modules and this feature is useful for our Node.js application as I’ve mentioned a bit earlier. The second one is needed to compile F# code to individual JavaScript files instead of a single bundle. This is also needed for Node.js only. And that’s basically it. As the last step you need to add a couple of tasks:

    "build:server": "fable-splitter src/api/api.fsproj -o dist/api --commonjs",
    "start:server": "yarn build:server && node dist/api/main.js"

Now you can start your own Node.js server written on F#.

conclusion

That’s basically it for today. In the next posts I’ll cover even more development topics, so please stay tuned. If you have any questions or faced any issues during following this manual don’t hesitate to write in the comments below.


Thanks a lot for reading! I hope you enjoyed it. If you find this material useful, don’t forget to subscribe and share with your colleagues and friends! Thanks!

One thought on “Getting Started with Fable. NodeJS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s