Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express.js server with functional programming (pure routes)

My goal is to be able to write pure routes for an Express.js server. Is that even possible?

For accessing the database and stuff I know I can use the fabulous Future monad to keep things pure, but how about the route rendering itself?

One of the greatest difficulties I am finding is that a route could end in quite a few different ways, like:

  • redirection
  • template rendering
  • error return
  • json return
  • file return

With the Future monad, I can handle the error and the success case, but there isn't much more granularity to the success case after that.

Is there any way to write pure and totally testable routes for Express.js?

like image 548
Marcelo Lazaroni Avatar asked Feb 17 '17 17:02

Marcelo Lazaroni


People also ask

Is Express JS functional programming?

Short answer: No - It is not possible. Explanation: In the context of functional programming, we have a data flow - the program takes some input data, transforms that and returns output data.

Is Express server good for production?

It's fast, unopinionated, and has a large community behind it. It is easy to learn and also has a lot of modules and middleware available for use. Express is used by big names like Accenture, IBM, and Uber, which means it's also great in a production environment.

Why should you separate Express APP and server?

Applying a similar concept to the project structuring of Express, the separation of the application logic from the server allows the code to be modular and follow a MVC (Model-View-Controller) model. The separation is essential to reduce coupling and to encapsulate and abstract the inside logic of application.

Is Nodejs functional programming?

Node programming is a more functional style of coding that improves code reliability and simplifies the debugging and testing process.


2 Answers

Short answer: No - It is not possible.

Explanation: In the context of functional programming, we have a data flow - the program takes some input data, transforms that and returns output data.

In case of a server, we have two data flows. First is when you are starting a server. In this flow, you might want to read config file or command line params from external world for stuff like port, host, database string, etc. This is a side effect and hence we would typically put it in Future. Example,

readJson(process.argv[2])    // Read configuration file
   .chain(app)               // Get app instance (routes, middlewares, etc.)
   .chain(start)             // Start server
   .run().promise()
      .then((server) => info(`Server running at: ${server.info.uri}`))
      .catch(error);

This is typical index.js file containing all side effects (reading config). Now let us turn to second data flow.

This data flow is slightly hard to imagine. The output/side-effect of first data flow is server listening on some port for external connection. Now imagine, that every request coming to this server as one independent data-flow in itself.

Just like index.js was the file meant to handle all the side effects, your route-file or route-handler function is meant to handle the side effect i.e. result request should be replied back at this route handler. Typically, a close-to-pure functional route would look like:

function handler(request, reply) {

    compose(serveFile(reply), fileToServe)(request)
        .orElse((err) => err.code === 'ENOENT' ? reply404(reply) : reply500(reply))
        .run();   // .run() is the side effect
}

return {
    method: 'GET',
    path: '/employer/{files*}',
    handler
};

In above snippet, everything is pure. Only impure stuff is .run() method (I am using Hapi.js and Folktale.js Task).

Same is the idea with Front-end frameworks like Angular or React. Components in these frameworks should contain all the effects/impurity. These components just like your route handlers are end-points where side effects should happen. Your models/services should be free from impurity.

Having said this, if you still want to make your routes perfectly pure, then there is hope. What you essentially want to do is - the more higher level of abstraction:

  1. If Express.js is a framework, then you should build your own abstractions on top of it.
  2. You would typically write your own generic route handler for all your routes and you will expose your own API to register routes.
  3. Your route handlers will then return future/task/observable or any other monad that contains all the side effects waiting to set free.
  4. Your abstracted route handler will then simply call .fork() or .run().
  5. What it means is that when you unit-test your route handlers, no side effects are happening. They are simply wrapped in some Async Monad.

In case of front-end frameworks, as I said earlier, that your UI components will have side-effects. But there are new frameworks that are going beyond this abstraction. Cycle.js is one framework I am aware of. It applies the higher level of abstraction than Angular or React. All the side-effects are caged in observable which are then dispatched to a framework and executed making all (I literally mean 100%) your components pure.

I have attempted to create server the way you are doing using Hapi.js+Folktale.js+Ramda. My idea originally traces its roots in Cycle.js. However, I achieved mild success. Some part code was really awkward and too restrictive to write. Afterward, I gave up on having pure routes. However, rest of my code is pure and is quite readable.

On a closing note, functional programming is about coding in parts and not coding in whole. What you or me are trying to do is functional programming all the way in whole. That will feel little awkward at least in JavaScript.

like image 196
Harshal Patil Avatar answered Oct 13 '22 01:10

Harshal Patil


I also don't think you can do that with Express.js, but I can suggest an alternative to it.

Web servers can be described as a function that takes a Request and provides a Response. However, if you check what happens in Express.js, you'll actually see that the signature is actually:

(IncomingMessage, ServerResponse) -> ()

For example:

const express = require('express')

const handler = (req, res, next) =>
  doSomethingAsync(req.body).then(res.json).catch(next)

express()
  .get('/', handler)
  .listen(3000, console.error)

While we'd prefere something like:

Request -> Promise Response

Paperplane is a lightweight NodeJS web server framework that works with the above type of signature. The idea is to use pure functions to transform the response you want to send. When it comes to routing, it has two functions that allow for a clean declaration:

routes :: { k: (Request -> Promise Response) } -> (Request -> Response)

and

methods :: { k: (Request -> Promise Response) } -> (Request -> Promise Response)

Check this simple example from the Paperplane's API docs :

const http = require('http')
const { mount, routes } = require('paperplane')

const { fetchUser, fetchUsers, updateUser } = require('./lib/users')

const app = routes({
  '/users': methods({
    GET: fetchUsers
  }),

  '/users/:id': methods({
    GET: fetchUser,
    PUT: updateUser
  })
})

http.createServer(mount({ app })).listen(3000)

It also provides other functions to deal with the difficulties you've encoutered.

  • Redirects
  • Working with JSON
  • Serving files

Plus, it supports Algebraic Data Types (ADTs), which you can get from the lovely Crocks lib.

like image 22
Yuri Carvalho Avatar answered Oct 13 '22 01:10

Yuri Carvalho