co0p

co0p

tech, culture & random thoughts

12 Dec 2021

doing microfrontends with nginx and ssi

According to the DevOps Report 2021 the DORA metrics give you a good indicator on the software delivery performance of a team or organization. If you want to be part of the “elite”, you have a high deployment frequency, it takes your team less than one hour to deploy a change into production, your time to restore a service is also less than one hour, and you have a low rate of deploying broken software to production.

microservices but the frontend is one monolith

Next to the long list of activities high-performing teams are doing, being as loosely coupled as possible rest of the organization is a key factor - not being slowed down by other parties. Deploying parts of your system independently is big lever to pull in achieving the elite status. Developing your software in a microservice architecture is the way to get fast, reliable and happy developers.

With microservices, ideally each backend team is loosely coupled from another, each team is happily deploying and developing for themselves. Each service has its own database, communication is done over an event-bus, and life is good.

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.

Defined by James Lewis and Martin Fowler 2014 (https://www.martinfowler.com/microservices/)

But what about the frontend? How can we introduce the benefits of microservices into the frontend? Of course, by putting the word micro in front of it! There is even a website, so it must be good: https://micro-frontends.org/. But how hard is it pull microfrontends off with existing technologies?

The goal is the following:

Let’s try it out.

Getting started with nginx + ssi

In order to get the microfrontend working, we need a place to stitch the individual pages together. This will allow us to have a “container” that ensures a common look and feel. Think Top-level navigation and or common footer. Luckily there exists a proven technology for that: Server Side Includes (ssi). The following html snippet uses ssi to include two files (page1.html and page2.html).

<html>
<body>
<!--# include file="page1.html" -->
<!--# include file="page2.html"-->
</body>
</html>

I am using nginx and use the following configuration to enable ssi:

events { }
http {
    server {
        location / {
            ssi on; # this is the important part!
            root /usr/share/nginx/html;
        }
    }
}

Nice, we got two pages rendered in one page. The html looks funny, but not bad for a start. Checkout commit d9994fd34a4b to play with the example

using ssi to include two pages

The SPA experience with nginx + ssi

In order to get the pages rendered based on the url, we can use a nginx capabilities to set variables which are then replaced when rendering the html. This decouples the individual page from the container. One step closer to our goal.

<html>
...
<main>
    <!--#include file="$PAGE.html" --> <!-- $PAGE will be set by nginx based on the url -->
</main>
</html>

Now we have to set the variable $PAGE based on the location.

events {} 
http {
    server {
       root /usr/share/nginx/html;
       listen 8080;
       server_name localhost;

       index index.html;
       ssi on;

       rewrite ^/$ /welcome redirect;

        location /welcome {
          set $PAGE 'welcome';
        }
        location /page1 {
          set $PAGE 'page1';
        }
        location /page2 {
          set $PAGE 'page2';
        }

        error_page 404 /index.html;
    }
}

Yeah! We have Single Page Application without using a fancy framework; just by using ssi. Checkout commit 499d57f785c7c to see the full working example.

using ssi to get the spa feeling

Stitching multiple frontends together

In order to get autonomous deployment working, I will be using docker in combination with docker-compose which allows me to run multiple webservers in the same network. The “container” container will be responsible to rendering the container website as well as doing the ssi based on the requested route. For each top-level page there is a container waiting to respond to requests.

microservices by internal routing

To get this working, a little nginx proxy magic is needed: Based on the requested url the variable $PAGE is being set to an internal address prefixed with /remote/. If a request to /page1/index.html hits the container, the $PAGE variable will be set to /remote/page1/index.html and ssi will try to include it. The request to the location /remote/page1/ is being proxied to the server team_page1_hostname:80801 which tries to find the file and returns it.

...
# define the servers which will serve the partials (hostname defined in docker container)
upstream team_page1 {
    server team_page1_hostname:8081;
}
...
location ~ ^/page1/(.*)$ {
    set $PAGE "remote/page1/$1";
    try_files $uri $uri/ /index.html;
}
...
# the internal locations which proxy to the respective servers
location ~ ^/remote/page1(.*)$ {
    internal;
    proxy_pass http://team_page1/$1;
}    

Done! We now have a working example of stitching multiple frontends together without relying on libraries - just by using the power of nginx and ssi. In theory each team can now own and deploy their own frontend and the container image / server is doing the stitching and routing.

microservices by internal routing

Since all frontends are being merged into one document and served to the browser, they can communicate via custom events with each other. They all have access to same runtime environment such as localstorage. This might come in handy, if you need want to do JWT authorization.

You can find the final version of the experiment at github.com/co0p/ssi-microfrontend-example.