snakey

An Obeserver-based web microframework for Node.js

Usage no npm install needed!

<script type="module">
  import snakey from 'https://cdn.skypack.dev/snakey';
</script>

README

Snakey is a Observer-based web microframework for Node.js, built using Rxjs. Inspired by Express.js.

Why?

I love Express.js. I love how simple it is to get an application up and running, but I dislike using callbacks for everything, and I dislike imperative programming. I searched far and wide for a callback-free web framework for Node.js, and was disappointed when I was unable to find anything. Thus, Snakey was born.

Snakey is for people like me who want to prevent themselves from getting trapped in Callback Hell, and also want to reap the benifits of Javascript's functional programming capabilities.

Installation

Note that Snakey is still very much experimental.

npm install snakey

or

yarn add snakey

Roadmap

  • Convert url-params to TypeScript
  • Add a functional API for building responses

Documentation

Quickstart

Snakey provides you with an Observable stream of HTTP requests, and functional programming tools to shape the stream into whatever your app needs.

import {of} from 'rxjs';
import {bite, snake, Context, applySnakes} from 'snakey';
import {textResponse} from 'snakey/response';

const app = [
  snake<Context>()
    .chain(bite('GET', '/'))
    .chain(textResponse('Hello World!'))
];

const {server} = applySnakes(app);
server.listen(9000);

Snakes

At the core of Snakey is the Snake type. A Snake is simply an OperatorFunction with a chain method. chain composes the Snake with a given OperatorFunction to form a new Snake. Because Snakes build on each other, this effectively creates a type-safe series of Operators.

export interface Snake<T, R> extends OperatorFunction<T, R> {
    chain<N>(op: OperatorFunction<R, N>) : Snake<T, N>
}

Creation

Snakes can be created using the snake function. This function takes an optional OperatorFunction and lifts it to a Snake.

import {snake} from 'snakey/snake';
import {map} from 'rxjs/operators';

snake<String>(); // Snake<String, String>
snake<Number, String>(map(n => String(n))); // Snake<Number, String>

applySnake

The applySnake function takes an array of Snakes and generates multiple Observables for each Snake. These Observables are then connected to a Node http.Server, and subscribed to.

export function applySnakes(snakes: Snake<Context, Responder>[], 
                            server: Server = new Server(),
                            observer = new ResponderObserver): SnakeResult;

The return value of applySnake is an object of three fields that contain the server, the generated Observables and the Subscriptions.

export type SnakeResult = {
  server: Server,
  streams: Observable<any>[]
  subscribers: Subscription[]
}

You can supply your own Observer to use, but the default behavior is to call a function. This does mean that your Streams should eventually return a function. This function is referred to as the Responder.

Context

The beginning of all requests is Context. applySnake automatically converts all requests into Context objects.

Properties

attributes name type description
readonly request http.IncomingMessage The request object recieved from Node.
readonly response http.ServerResponse The response object recived from Node.
readonly uri uri-js:URIComponents Parsed request URI.
readonly pathMatch PathMatch RegExpExecArray

Methods

match(pattern: string | RegExp): Context

If uri.path matches pattern a new Context object with pathMatch set will be returned. Otherwise, this will be returned.

bite(verb: string, pathPattern: string | RegExp)

bite is an operator for Observable<Context>. It creates an Observable<Context> that matches the verb and pathPattern. Path matching is done using Context.match, so any parameters or RegEx groups are preserved in the pathMatch property of Context.

Responder

Snakey provides its own objects for writing responses to client. The one you'll likely be using most oftens is the Responder class. Indeed, this expected return value for every Snake.

The Responder class is simply the collection of data needed to construct and send a response to the client. It provides at least one method: respond which constructs and sends this response. This method is called by the default Observer.

Properties

attributes name type description
readonly resObj http.ServerResponse The response object recieved from Node.
readonly body string {toString(): string}
readonly status number The HTTP status code of the response to send to the client. (default: 200)
readonly headers HeaderMap The HTTP headers to write to the client.
readonly encoding string The encoding of the body. (default: 'UTF-8')
readonly endResponse bool If true, the call to respond will terminate the connection with the client. (default: true)

response(): void

Writes the HTTP response to the client. If endResponse evaluates to true the connection will be terminated.