The Express Module

Provides an environmental effect that wraps and Express application

Installation

yarn add express @matechs/express
yarn add -D @types/express

Module

import newExpress from "express";
import { effect as T } from "@matechs/effect";
import * as EX from "express";
import * as bodyParser from "body-parser";
import { Server } from "http";

// environment entries
export const expressAppEnv: unique symbol = Symbol();
export const expressEnv: unique symbol = Symbol();
export const requestContextEnv: unique symbol = Symbol();

// environment to hold the app instance
export interface HasExpress {
    app: EX.Express;
  };
}

// http method
export type Method = "post" | "get" | "put" | "patch" | "delete";

// effect
export interface Express {
    withApp<R, E, A>(op: T.Effect<R & HasExpress, E, A>): T.Effect<R, E, A>;
    route<R, E, A>(
      method: Method,
      path: string,
      f: (req: EX.Request) => T.Effect<R, RouteError<E>, RouteResponse<A>>
    ): T.Effect<R & HasExpress, T.NoErr, void>;
    bind(
      port: number,
      hostname?: string
    ): T.Effect<HasExpress, T.NoErr, Server>;
  };
}

// describe an errored response
export interface RouteError<E> {
  status: number;
  body: E;
}

// create error response
export function routeError<E>(status: number, body: E): RouteError<E> {
  return {
    status,
    body
  };
}

// describe successful response
export interface RouteResponse<A> {
  status: number;
  body: A;
}

// create successful response
export function routeResponse<A>(status: number, body: A): RouteResponse<A> {
  return {
    status,
    body
  };
}

// provides a new app into env of op
export function withApp<R, E, A>(
  op: T.Effect<R & HasExpress, E, A>
): T.Effect<Express & R, E, A>

// describe the environment used to carry current request
export interface RequestContext {
  [requestContextEnv]: {
    request: EX.Request;
  };
}

// bind a new route to express
export function route<R, E, A>(
  method: Method,
  path: string,
  handler: T.Effect<R & RequestContext, RouteError<E>, RouteResponse<A>>
): T.Effect<R & HasExpress & Express, T.NoErr, void> 

// listen on hostname:port
export function bind(
  port: number,
  hostname?: string // default 127.0.0.1
): T.Effect<HasExpress & Express, T.NoErr, Server>

// access express app
export function accessAppM<R, E, A>(
  f: (app: EX.Express) => T.Effect<R, E, A>
): T.Effect<HasExpress & Express & R, E, A> 

// access express app purely
export function accessApp<A>(
  f: (app: EX.Express) => A
): T.Effect<HasExpress & Express, T.NoErr, A> 

// access request
export function accessReqM<R, E, A>(
  f: (req: EX.Request) => T.Effect<R, E, A>
): T.Effect<RequestContext & Express & R, E, A>

// access request purely
export function accessReq<A>(
  f: (req: EX.Request) => A
): T.Effect<RequestContext & Express, never, A>

// environment for consumer usage
export type ExpressEnv = HasExpress & Express;

// environment for consumer usage in request
export type ChildEnv = ExpressEnv & RequestContext;

// implementation
export const express: Express

Usage

import { effect as T, exit as E } from "@matechs/effect";
import * as EX from "@matechs/express";
import { Do } from "fp-ts-contrib/lib/Do";
import { pipe } from "fp-ts/lib/pipeable";

// create a new express server
const program = EX.withApp(
  Do(T.effect)
    .do(
      // bind GET / to {"message":"OK"}
      EX.route(
        "get",
        "/",
        T.pure(
          EX.routeResponse(200, {
            message: "OK"
          })
        )
      )
    )
    .bind("server", EX.bind(8081)) // listen on port 8081
    .return(s => s.server) // return node server
);

// construct live environment
const envLive = pipe(T.noEnv, T.mergeEnv(EX.express));

// run express server
T.run(
  T.provideAll(envLive)(program),
  E.fold(
    server => {
      // listen for exit Ctrl+C
      process.on("SIGINT", () => {
        server.close(err => {
          process.exit(err ? 2 : 0);
        });
      });

      // listen for SIGTERM
      process.on("SIGTERM", () => {
        server.close(err => {
          process.exit(err ? 2 : 0);
        });
      });
    },
    e => console.error(e),
    e => console.error(e),
    () => console.error("interrupted")
  )
);
$ curl http://127.0.0.1:8081/
{"message":"OK"}

Notes

The module is a work in progress and API is expected to be changed (not significantly).

Last updated