Refactor Node.js Routes with Testable, Functional Concepts like Composition, Currying, and Closures
By: Daniel Dughila
Tags: Currying, Functional Programming, Composition, Clojure, Functional Javascript

Light Functional JavaScript

Refactor Node.js Routes with Testable, Functional Concepts like Composition, Currying, and Closures

I love Node.js! It’s very fast, lightweight and lets you spin up applications with just a few lines of code.

I remember reading about the Node.js callback hell and how there’s no escape from it

There’s even a website called the callbackhell.com, lol.

What’s funny is that most times I don’t even realize how messy my code is until I try to test it.

Because I have no life, I once tried to retrieve some of my gists from the Github API, reduce them to their descriptions and URLs and sort them alphabetically by their description field.

No problem!

Here’s the mini express app

An app with 33 lines of code, amazing!

Here’s the hellish route which handles the gists logic I talked about

Hell o

Mmmm, I’m not even going to try testing it! I may add tests if you guys like this article and decide to punish me by insisting.

But wait, there must be light at the end of the tunnel. I can’t leave my code untested!

Could there be a way to simplify it?

What luck, it turns out functional programming and good, opinionated programming practices can help

Let’s refactor this code together!

Step one, separation of concern

I don’t like the fact that our code instantiates the Express route, pulls in dependencies and handles business logic, all at once. Let’s fix this!

Voila!

Separated root wiring form handling logic

Express is expressing itself, while we handle our route logic in the handleGistsRoute function.

Step two, function currying

Currying means to break down a function that takes multiple arguments into a series of functions

HandleGistsRoute encapsulating it’s dependencies

HandleGistsRoute results from the curried handleGistsRouteFactory which accepts a set of dependencies and returns another function, ready to receive the gistsUrl from its caller, the Express route.

The last function returned by the factory will get called by Express with the req, res, next arguments.

Wow, this will take a while to sip in. Maybe watch this videos about currying, closures and higher order functions to understand the concepts better.

Unit tests use the handleGistsRouteFactory while the rest of the app uses the actual handleGists method, precompiled with its dependencies (saving us the trouble of passing them around).

Step three, extracting pure functions

Notice that handleGistsRouteFactory is now a pure function (almost, because it calls next instead of returning output), receiving all its data through parameters (easy to mock in our tests).

A pure function is a function where the return value is only determined by its input values, without observable side effects

My critical eye tells me that we can extract the description, filtering and URL generating logic from our handleGistsRouteFactory.

This means we can extract the getDescription, sortByName, getNextUrlFactory, and getNextUrl PURE functions from our handleGistsRouteFactory.

Stripped out factory

We took quite a lot of the handleGistsRouteFactory shoulders. The function does not worry about processing and sorting gists or about knowing which URLs to access anymore.

Already our function looks great. But we can do much better!

Step four, async await

Async await is syntactic sugar for promises, allowing us to put more emphasis on what the code does instead of how.

Klein, means small in German :)

HandleGistsRouteFactory is now 20 lines of code, sweet!

The engineer in me sees lines 34 and 35 as possible candidates for recursion. What if we could get as many gists pages as we wanted?

Step five, over engineered recursion

If it comes naturally to you, recursion can be a great way to do declarative programming, which IMO, is a broader paradigm which includes FP as well.

We’ve extracted the get gists logic into the async, recursive, over-engineered getGistsFactory, just because we can!

Browse the code for a minute. I think it’s easier to read and I know it’s easier to test.

Step six, Testing the beast

Without further ado, let’s test with Jest! Ooo, it rhymes!

That’s it!

Every method is tested and ready for production.

I used a quick and fancy method of mocking which you can read about here.

The code for this article is on Github, please check it out.

I hope you enjoyed my article, please clap if you did so others can notice it too!

Everything you read is opinionated, so please comment and share your ideas!