Today I’m going to tell you about a new project THAT I recently developed, Farrow. Node.js is a type-friendly functional style Web services framework.

Project motivation

In the current Node.js open source ecosystem, there are express. js, Koa, EggJS, Hapi, Restify, Fastify, NestJS, and perhaps countless other Web services frameworks. Do we need another?

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

Most of the popular Web services frameworks in Node.js were developed from a JavaScript perspective before TypeScript became really popular.

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

If we include Static type-system capabilities to catch potential problems in compile-time as well as Web frameworks, we can redevelop a Web services framework in TypeScript. Might be something worth trying.

Farrow is one of my outputs in this direction.

Middleware design from TypeScript perspective

I was inspired when Rollup and Svelte author Rich Harris recently shared his thoughts on the Next-Gen Node HTTP APIs *[0]*.

It all started with a tweet from Wes:

Expressjs-style middleware function design was chosen by nearly 70% of developers. An overwhelming choice.

Rich Harris had 14.5% of the vote.

In that Gist, Rich Harris explains why he doesn’t like the first option. Is roughly:

1. Always need to pass the RES argument ugly

2. When composing middleware, you often monkey-patching res

He suggested another design that he thought was better.

To put it simply, cancel the res parameter, keep only the REq parameter, express the response result through return response, and call the next middleware next() through return void/undefined expression.

Another developer, Oliver Ash, also pointed out on Twitter that ExpressJS’s middleware design has a weakness — it doesn’t take full advantage of Compile Time’s screening capabilities *[1]*.

Twitter.com/OliverJAsh/…

Simply put, when Response is the middleware return value, TypeScript can type constrain that every request must have a return value without fear of omission.

Giulio Canti, author of FP-TS, also had his own attempt on this — Hyper-TS [3]. Hyper-ts is inspired by the Purescript Hyper project and uses TypeScript type-System to avoid common errors such as:

These clues all lead to the conclusion that it is probably possible to design HTTP middleware apis immutable.

Farrow-pipeline: Type-friendly middleware function design

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

From the above picture, we can learn the following:

1) Response is not in the parameters of the middleware function, but a naive function exported from the Farrow-HTTP module.

2) Response is the return value of the middleware function, which can be checked at compile-time.

If no value is returned, it looks like this:

If an error value is returned, something like this would happen:

Response.{method}() must be used to respond to the client:

The API design of Response, which supports Method Chaining, can be called as follows:

As above, setting response Status, setting Response Headers, setting response cookie, and setting Response Content can all be elegantly written together.

So how do multiple middleware pieces collaborate in Farrow?

For example, in the upstream middleware, pass a new request to the downstream middleware like this:

The second argument to Farrow middleware functions is the next function. Unlike expressJS/Koajs middleware functions, Farrow middleware functions have both arguments and return values.

The argument is an optional Request and the return value is Response.

When the next() call passes no parameters, the downstream middleware gets the same request as the upstream middleware.

When a new request is passed when next is called, the downstream middleware gets the new Request object.

With this natural parameter passing mechanism, we don’t have to modify the current request. Farrow even sets the request type to readonly.

Farrow encourages keeping request/ Response IMmutable.

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

Response objects provide a merge method to easily merge status, headers, cookies, content and other components of multiple responses.

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

Farrow-schema: type safe Routing design

Farrow implements a powerful and flexible schema-based Validation that matches a specific request object in a type-safe manner.

The basic usage is as follows:

The http.match method accepts {pathname, method, Query, Params, headers, cookies} objects to form a Request Schema.

Schema. pathname uses the expressjs-like path-to-regexp notation.

Farrow can infer the exact type of matched Request objects from the Request Schema and perform validate in the Runtime to ensure the type security of Request objects.

Farrow also implements type-safe route matching based on TypeScript V4.1’s Template Literal Types feature.

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

A more complex example looks like this:

When key:type appears in? Before, it was treated as part of params, and the order was sensitive.

When key:type appears in? Later, it is treated as part of query, where the order is not sensitive.

To learn more about Farrow’s router-URL-Schema, see its documentation [3].

Farrow – Hooks mechanism

Another notable feature in Farrow is that we referenced the react-hooks design and provided farrow-hooks to integrate other parts of the server, such as logger, database-connection, etc.

In Farrow, context is not a mutable CTX argument; it is Hooks.

Like react-hooks’ useState, it can be thought of as a fine-grained cut of this.state shared in class-component.

The context. use in Farrow does the same for the shared CTX. As follows:

We define a User type that creates a Farrow Context in a manner similar to react.createcontext and provides a default value (null here).

The corresponding user context is accessed through the built-in hooks of userContext.use (). In each request -> Response, all contexts are new and independent.

Instead of having one large CTX, we have multiple small Context cells.

We can wrap custom hooks based on context.use (), as in useUser above.

Dynamically update the value of the Context as follows.

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

Then use farrow-hooks as you would use react-hooks, as follows:

With the Context Hooks mechanism, our middleware function type is always simple and stable. It only focuses on handling request -> Response. Additional things can be provided by Hooks on demand.

Farrow-react: Built-in componentized SSR

Farrow provides an official SSR library, Farrow-React. Of course, you can also build your own SSR library based on response. HTML or Response.stream.

As shown above, farrow-react provides a farrow-hooks function. Using useReactView, we get a ReactView object, and using its render method, JSX can be rendered into HTML and sent to the browser via farrow-HTTP.

Farrow-react provides a Link component to help us handle prefix-related auto-completion. To learn more, check out farrow’s official documentation.

conclusion

So far, we’ve outlined some of Farrow’s core features.

Farrow’s goal is not to stop there, we will build more Farrow ecology in the future. Such as:

1) Farrow-restAPI and farrow-restapI-client, support the reuse of server project schema/type in client project, Realize the Type Safe function of server/client data transfer.

Farrow-graphql and Farrow-GraphQL-client, similar to farrow-restAPI but supported by graphQL.

3) Farrow-server-component supports React Server Component.

There’s a lot of work to be done, but if you’re interested, please come and contribute to Farrow *[4]*.


[0] Next-gen Node HTTP APIs

Twitter.com/Rich_Harris…

[1] Oliver Ash

Twitter.com/OliverJAsh/…

[2] hyper-ts: Type safe middleware architecture for HTTP servers

Github.com/gcanti/hype…

[3] Farrow Router-Url-Schema

Github.com/Lucifier129…

[4] Farrow: A type friendly web framework for node.js written by TypeScript

Github.com/Lucifier129…