Inside Aircall’s powerful and complex front end apps

Hi, I’m Xavier, tech co-founder at Aircall. Our awesome team of 40 (half-tech, half biz) is building the best phone system for companies.
We’ve just
raised $8M and are looking to grow our offices in NYC and Paris. Checkout our 20+ offers!

This blog post describes technical choices we have made to build our web apps.

Disclaimer: this is not an every-apps-must-be-built-like-this post. Build yours to fit your technical needs and with tools your team will be comfortable with. Ping me if you know how to make our stack even better :)


Back in early 2014

When we started building Aircall, we were trying to create a simple, flexible and hardware-free phone system for small teams & SMBs. Our target account would have up to 10 teammates, around 5 numbers, ~200 imported contacts and would make and receive 100 calls/day max.
Not kidding, this was even written in our Terms & Conditions of use:

In addition, Aircall is not recommended for massive bulk call-in lines (e.g., customer support or sales call-centers handling over hundreds of simultaneous calls).

Situation now

Today, our goal hasn't changed: provide a simple, flexible and hardware-free phone system for teams. Our target customer though is different: call-centers, sales teams & larger companies.

From a business point of view, we simply changed our target customer and the pricing of our plan. But the technical impact was completely different: our infrastructure wasn't at all prepared for this massive flow of data and the UX of our product ended up completely broken. As a result, our apps started to feel slow and laggy for our customers: feeling like a buggy experience overall.

For example following this shift, big companies wanted to import their contact database, meaning more than 2M entries to process, sort, search in and display. Or call-centers needed to handle more than 10k calls a day, with 200 agents, simultaneously using the app and making operations on it. And of course, every action had to happen in real time.

It was definitely a big mistake not to anticipate this massive usage, but we were in such a rush to sustain our business growth that we were oblivious to this problem. Beginning of 2016, we had to face it: our product was broken. We had to build it again, nearly from scratch.

How we build complex and powerful apps

Back then, we knew our product was not usable for our new target customer and in the meantime, our customer growth was unstoppable. We knew our users spent their entire day using our apps, making them the critical part of our product. We wanted them to be as fast and easy to use as possible, without any buggy effects. This was our chance to start over and rebuild our front end apps from scratch.

On the tech side, this meant starting a new project with a clean and evolutive code base, new concepts and good practices, a new internal API that could handle massive data rendering, and a new deployment process.

To make it clear, the apps we needed to rebuild were:

  1. The dashboard - where admins can setup phone numbers & invite teammates;
  2. The phone - where agents can make/receive calls and handle their shared contact book.
Aircall SPAs

To increase the challenge, we wanted to ship each project to all our customers in 3 months: tough one knowing that the front end team was only composed of 4 people at that time.

A TypeScripted AngularJS app

We kept AngularJS (1.5) for one main reason: we master it. We know by heart how to use it, how to build a huge clean code base, and how to make the best of it. Of course, we had a look at other web frameworks like React and Ember, but we were not really confident with them. Plus, it is easier to hire talented people in AngularJS than in other frameworks (at least in France).

We wanted our code to be scalable, understandable and easy to maintain, and TypeScript was the best solution.

  • It adds static typing and inheritance to your code;
  • It provides compile time error checking, which is a major life changer to prevent errors before they happen in your browser;
  • It's useful for maintaining your code base clean and understandable between several engineers;
  • It's easy to learn as it’s exactly the same syntax as JavaScript.

I’m pretty sure we’re using only 60% of TypeScript functionalities, but it saved us dozens of hours of code, prevented hundreds bugs and made our apps really easy to manage from a engineering point of view. Yes, we have to say it: good job Microsoft.

Built with Webpack

Webpack is a module bundler. It takes all your files and dependencies and builds them into one single file that you can host on your server. It comes with a lot of powerful plugins (full list here). We setup Webpack to:

  1. Compile TypeScript code into JavaScript;
  2. Inject all dependencies (AngularJS and stuff);
  3. Minify in one file and uglify JS code;
  4. Compile and minify SASS code into CSS;
  5. Prepare GZiped files.

Note that we didn't use Bower, nor any "project kickstarter tool" such as Yeoman and Mimosa. We think the less tools you use, the more maintainable your project will be.

Tested continuously with CircleCI

We needed a continuous development environment to build, test and deploy our apps in all different environments.

CircleCI is a continuous integration and delivery SaaS tool, like Codeship or Jenkins. When we push code on a Git branch, CircleCI starts a virtual container, clone the repository into it and follow a set of instructions we defined. With that in mind, we setup CircleCI to:

  1. Build the app with Webpack;
  2. Lint our TypeScript code (TSLint) and our Sass files (sass-lint). Rules we defined are a bit rude, but allow us to have a unique code style among all of us;
  3. Launch our tests suite. For now, only End-to-End tests are written, using Protractor;
  4. Create a new CodeDeploy revision and deploy it to AWS (see section below).

If one step fails, the build will stop and code will not be deployed on our servers. If everything is green, then we just deployed a new version to production in less than 5min :).

Deployed on AWS instances thanks to CodeDeploy

Once the application is built, CircleCI will create a zip file containing your app’s compiled assets and will upload it in a Amazon S3 bucket. Then, CodeDeploy will automatically detect this new revision of your app and will install it on Amazon EC2 instances. As our apps are just static assets, CodeDeploy installation is just:

  1. Install nginx if it’s not already on the instance;
  2. Remove files of the previous app version;
  3. Unzip the S3 zip file on the server and move all those compiled files into a /www/app_name folder;
  4. Setup nginx with proper SSL certificate and config;
  5. Restart nginx.

Of course, servers are disconnected one at a time from your Load Balancer during the installation, so no downtime for your app.

CodeDeploy applications can have several Deployment Group: virtual groups with specific role and configuration — we use them to split our staging and production environments. In our setup, every Deployment Group has two EC2 instances associated, and those servers are behind a Load Balancer (Amazon ELB). You'll find more info about CodeDeploy here.

CircleCI has a native CodeDeploy integration which is quite easy to setup. It will save you some time as you won't have to write the S3 deployment scripts and hooks yourself.

Rendering apps with Nginx

What’s really convenient in setuping Nginx on every app’s deployment is that you can have Nginx' conf in your codebase and it will automatically be updated to your servers every time you deploy a new version. No need to ssh yourself on every instance to setup your new Nginx conf. You may think it’s overkill, but it will save you lots of time if you add a totally new instance in your CodeDeploy Deployment Group.

As Webpack previously built gziped files, we just needed to allow gzip in our nginx configuration for JavaScript and CSS files. Don’t miss this step, it greatly improves your app rendering time: we’ve come from a 1.6MB JavaScript compiled file to a 300KB file only thanks to this.

Last thing, we only use Nginx to render static files like HTML, CSS, JavaScript and images. When a final user goes to our app’s URL, there's nothing but Nginx rendering the basic index.html file, which loads the assets it needs: simple :) No need for a NodeJS, Ruby or whatever server behind.

And a f*cking good team

To help you build the best app, you will need a team of talented minds. The kind of people who won’t sleep until they find the best solution on the product. The same people that will scratch their head to find the smallest speed improvement and will come to the office the next day yelling: “NAILED IT!”. And recruiting that kind of people might be the hardest thing you'll have to do.

At Aircall, we've just finished our biggest challenge ever as a team: re-building from scratch both frontend apps and improving them to make them bulletproof. The best part is yet to come though as more and more companies start using Aircall every day. We’ll always need to improve loading times, realtime logic, add some new sexy feature or invent a new way to handle phone calls. Good news is, we’ll keep doing it until we trash every single old black Cisco phone sitting on companies’ desks.


TL;DR

  • AngularJS app written in TypeScript and built with Webpack
  • Tested & deployed with CircleCI and CodeDeploy, hosted on AWS instances behind a load balancer
  • Nginx as web server, rendering only HTML and assets
  • Lots of sweat, hundreds of hours of intensive work and impressive teamwork. Congrats Maïa, Marc, Maxence and Gi!

It surely can be improved, but this is for us, the easiest to set up, most elegant and productive app development workflow we wanted to work with :)

Cheers,
Xavier