snakey
Version:
An Obeserver-based web microframework for Node.js
136 lines (90 loc) • 5.68 kB
Markdown
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](https://rxjs.dev/api/index/class/Observable) stream of HTTP requests, and functional programming tools to shape the stream into whatever your app needs.
```ts
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](https://rxjs-dev.firebaseapp.com/api/index/interface/OperatorFunction) with a `chain` method. `chain` composes the `Snake` with a given `OperatorFunction` to form a new `Snake`. Because `Snake`s build on each other, this effectively creates a type-safe series of Operators.
```ts
export interface Snake<T, R> extends OperatorFunction<T, R> {
chain<N>(op: OperatorFunction<R, N>) : Snake<T, N>
}
```
### Creation
`Snake`s can be created using the `snake` function. This function takes an optional `OperatorFunction` and lifts it to a `Snake`.
```ts
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 `Snake`s and generates multiple `Observable`s for each `Snake`. These `Observable`s are then connected to a Node `http.Server`, and subscribed to.
```ts
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 `Observable`s and the `Subscription`s.
```ts
export type SnakeResult = {
server: Server,
streams: Observable<any>[]
subscribers: Subscription[]
}
```
You can supply your own [Observer](https://rxjs.dev/api/index/interface/Observer) to use, but the default behavior is to call a function. This does mean that your `Stream`s 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 | null = null | Parsed path against a pattern. This is set by the `match` function. |
### 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} | The body of the response to send to the client. |
| 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.