Farrow: A new web framework in the new year
Tags: node, typescript, react
Published: 2021-01-01 13:28:59

Today I'd like to introduce you to a new project I've recently developed - [Farrow](https://github.com/Lucifier129/farrow). a type-friendly functional style Node.js web framework.

## Motivation

In the current Node.js open source ecosystem, there are already expressjs, koajs, hapi, restify, fastify, nestjs, and perhaps countless other web services frameworks, so do we need another one?

The answer may vary from person to person, so I'd like to share my personal opinion here.

Most of the popular web service frameworks in Node.js were developed with a JavaScript perspective before TypeScript became really popular.

They take full advantage of the expressive power of JavaScript's dynamic typing, no doubt about it.

If we take into account the ability of the Static Type-System to capture as many potential problems as possible in Compile-Time, then redeveloping a Web services framework in TypeScript might be a worthwhile endeavor.

Farrow is one of my outputs in this direction.

## Middleware design from a TypeScript perspective

Rich Harris, the author of Rollup and Svelte, recently shared his thoughts on [Next-gen Node HTTP APIs](https://twitter.com/Rich_Harris/status/1304082928490733573), and I was inspired.

It started with a poll tweeted by Wes.

![a poll tweeted by Wes](https://dev-to-uploads.s3.amazonaws.com/i/592g6n0qcnilhfdpvxq8.png)

Close to 70% of developers, opted for expressjs style middleware function design. An overwhelming choice.

Rich Harris' choice, with only 14.5% support.

In that Gist, Rich Harris explains why he doesn't like the first option. Roughly, it goes as follows.

- Always need to ugly pass `res` parameters
- When combining middleware, you often have to do monkey-patching on `res`

He gave what he felt was a better alternative design.

![a better alternative design](https://dev-to-uploads.s3.amazonaws.com/i/cj4y2y9ixp5y4dgkx3wh.png)

Simply put, the `res` parameter is eliminated, only the `req` parameter is retained, the response result is expressed by return response, and the next middleware `next()` is called by return void/undefined.

Another developer, Oliver Ash, tweeted about one of the shortcomings of expressjs' middleware design - it does not take full advantage of Compile-Time's troubleshooting capabilities.

![a tweet of Oliver Ash](https://dev-to-uploads.s3.amazonaws.com/i/tm3lytpc372m23x2theh.png)

In brief, when the response is the return value of middleware, TypeScript can type-check that each request must have a return value without fear of omission.

Giulio Canti, the author of fp-ts, also has his own attempt - [hyper-ts](https://github.com/gcanti/hyper-ts). inspired by purescript's hyper project, hyper-ts uses TypeScript's Type- System to circumvent some common errors, such as: 

![some common errors](https://dev-to-uploads.s3.amazonaws.com/i/lhm2ez67q0i8aypfqyg3.png)

These clues all point to the conclusion that it may be possible to design the HTTP middleware API in a functional style (immutable way).

## Farrow-Pipeline: Type-friendly middleware function design

Farrow's middleware functions are inspired by Koa middleware, but are different.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/z73ca7v3jlaos8ttvjad.png)

From the above figure, we can learn the following information.

- `Response` is not in the parameters of the middleware function, but from the plain function exported by farrow-http module.

- `response` is the return value of the middleware function, which can be checked in Compile-Time.

If there is no return value, it will look like the following.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xxf4nt1zzlidnl38zh34.png)

If an incorrect value is returned, it will look like the following.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/t0v42vy8nl32ukaryglq.png)

The response to the client must be made by means of `Response.{method}()`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gni8qn1xxxi2vqs6bxkg.png)

Response's API is designed to support Method Chaining, which can be called as follows.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nwyewwr9zjcy41elnf7v.png)

As above, setting response status, setting response headers, response cookies, and response content can all be written together elegantly.

So, how does multiple middlewares collaborate with each other in Farrow?

For example, in the upstream middleware, pass a new request to the downstream middleware, like the following.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3yhuyep14xjfblikh9g8.png)

The second parameter of the Farrow middleware function is the `next` function. Unlike expressjs/koajs middleware functions, the Farrow middleware function has both parameters and return values.

Its parameter is the optional `request` and its return value is `response`.

When the `next()` call is made without passing parameters, the downstream middleware gets the same `request` as the upstream middleware.

If a new `request` is passed when `next` is called, the downstream middleware will get the new `request` object.

With this very natural parameter passing mechanism, we don't need to modify the current request. Even, Farrow sets the request type to read-only.

Farrow encourages keeping the request/response immutable.

Similarly, we can filter or manipulate the response returned by the downstream middleware in the upstream middleware, as follows.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eopw3ynymipui7pdmky0.png)

The Response object provides a merge method to easily merge the status, headers, cookies, content, and other components of multiple responses.

Farrow also provides a fractal-enabled Router design that helps us to fine-grained segment business logic into different modules and organically combines them.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/iuzees3xuf37qdxrq80w.png)

## Farrow-Schema: Type-Safe Routing Design

Farrow implements a powerful and flexible Schema-Based Validation that can match specific request objects in a type-safe manner.

The basic usage is shown below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7k3bvnfal8ey2klihuk8.png)

The `http.match` method accepts parameters as `{ pathname, method, query, params, headers, cookies }` objects to form a `Request Schema`.

`schema.pathname` adopts expressjs-like style via `path-to-regexp`.

Farrow will extract the exact type of the matching request object through type infer according to the Request Schema, and validate it in the runtime to ensure the type safety of the request object.

In addition, Farrow also implements type-safe route matching based on the Template Literal Types feature of TypeScript V4.1.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/e302q8yh3lk610vtfeju.png)

With the <key: type> format in the path, we can combine { pathname, params, query }, write only one path, and extract the corresponding type from the path by type infer.

A more complex case is shown below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/icp0hadjywozhe9ztu0i.png)

- When `<key:type>` appears before `?` is treated as part of `params`. The order is sensitive.

- When `<key:type>` appears after `?` appears after it, it is treated as part of the `query`, where the order is not sensitive.

To learn more about Farrow's Router-Url-Schema, you can check out its [documentation](https://github.com/Lucifier129/farrow/blob/master/docs/http.md#router-url-schema).

## Farrow-Hooks mechanism

Another noteworthy feature in Farrow is that we take a cue from React-Hooks and provide a Farrow-Hooks mechanism to integrate with other parts of the server, such as logger, database-connection, etc.

In contrast to koajs, which mounts extended methods with mutable `ctx` arguments, in Farrow, the context is not an argument, but a Hooks.

Like React-Hooks `useState`, it can be seen as a fine-grained slicing of the `this.state` shared in the class-component.

Context.use in Farrow cuts the shared ctx in the same way. This is shown below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m8mjh950pmh73jizx52r.png)

We define a User type, create a Farrow Context in a similar way to React.createContext, and provide the default value (in this case, null).

UserContext.use() is a built-in hook that provides access to the corresponding user context, and all Contexts are new and independent during each request -> response.

Instead of having one big ctx, we have multiple small Context units.

We can wrap custom hooks based on `Context.use()`, such as `useUser` in the above image.

To dynamically update the value of a Context, do something like the following.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/i1fe7rmcxyjuh3bo4w1k.png)

Implement a Provider Middleware that dynamically and asynchronously updates the context value for consumption by the downstream middleware. The relationship is similar to that of Provider and Consumer in React Context. The upstream middleware is the Context Provider and the downstream middleware is the Context Consumer.

With the Context Hooks mechanism, our middleware function type is always simple and stable, it only focuses on request -> response processing, other additional things can be provided through Hooks on demand.

## Farrow-React: A built-in component-based SSR

Farrow provides an official SSR library, farrow-react, but you can also build your own SSR library based on methods like `Response.html` or `Response.stream`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/c86xtyhiq6msyq7w67b2.png)

As you can see above, `farrow-react` provides a Farrow-Hooks and through `useReactView` we get a ReactView object that renders JSX into HTML and sends it to the browser through `farrow-http`.

`farrow-react` provides a `Link` component to help us handle prefix-related auto-completion. To learn more, check out the official farrow [documentation](https://github.com/Lucifier129/farrow/blob/master/docs/react.md).

## Summary

At this point, we have broadly described a few of Farrow's core features.

Farrow's goal doesn't stop there, we will build more farrow ecosystem in the future. For example.

- `farrow-restapi` and `farrow-restapi-client` support reusing the schema/type of the server project in the client project to achieve type-safe functionality on the server/client side of the data transfer.

- `farrow-graphql` and `farrow-graphql-client`, similar to `farrow-restapi` but with support for implementation via graphql.

- `farrow-server-component`, supports React Server Component.

There is still a lot of work to be done, so if you are also interested, feel free to contribute to the [Farrow](https://github.com/Lucifier129/farrow).