The Express Module
Provides an environmental effect that wraps and Express application

Installation

1
yarn add express @matechs/express
2
yarn add -D @types/express
Copied!

Module

1
import newExpress from "express";
2
import { effect as T } from "@matechs/effect";
3
import * as EX from "express";
4
import * as bodyParser from "body-parser";
5
import { Server } from "http";
6
7
// environment entries
8
export const expressAppEnv: unique symbol = Symbol();
9
export const expressEnv: unique symbol = Symbol();
10
export const requestContextEnv: unique symbol = Symbol();
11
12
// environment to hold the app instance
13
export interface HasExpress {
14
app: EX.Express;
15
};
16
}
17
18
// http method
19
export type Method = "post" | "get" | "put" | "patch" | "delete";
20
21
// effect
22
export interface Express {
23
withApp<R, E, A>(op: T.Effect<R & HasExpress, E, A>): T.Effect<R, E, A>;
24
route<R, E, A>(
25
method: Method,
26
path: string,
27
f: (req: EX.Request) => T.Effect<R, RouteError<E>, RouteResponse<A>>
28
): T.Effect<R & HasExpress, T.NoErr, void>;
29
bind(
30
port: number,
31
hostname?: string
32
): T.Effect<HasExpress, T.NoErr, Server>;
33
};
34
}
35
36
// describe an errored response
37
export interface RouteError<E> {
38
status: number;
39
body: E;
40
}
41
42
// create error response
43
export function routeError<E>(status: number, body: E): RouteError<E> {
44
return {
45
status,
46
body
47
};
48
}
49
50
// describe successful response
51
export interface RouteResponse<A> {
52
status: number;
53
body: A;
54
}
55
56
// create successful response
57
export function routeResponse<A>(status: number, body: A): RouteResponse<A> {
58
return {
59
status,
60
body
61
};
62
}
63
64
// provides a new app into env of op
65
export function withApp<R, E, A>(
66
op: T.Effect<R & HasExpress, E, A>
67
): T.Effect<Express & R, E, A>
68
69
// describe the environment used to carry current request
70
export interface RequestContext {
71
[requestContextEnv]: {
72
request: EX.Request;
73
};
74
}
75
76
// bind a new route to express
77
export function route<R, E, A>(
78
method: Method,
79
path: string,
80
handler: T.Effect<R & RequestContext, RouteError<E>, RouteResponse<A>>
81
): T.Effect<R & HasExpress & Express, T.NoErr, void>
82
83
// listen on hostname:port
84
export function bind(
85
port: number,
86
hostname?: string // default 127.0.0.1
87
): T.Effect<HasExpress & Express, T.NoErr, Server>
88
89
// access express app
90
export function accessAppM<R, E, A>(
91
f: (app: EX.Express) => T.Effect<R, E, A>
92
): T.Effect<HasExpress & Express & R, E, A>
93
94
// access express app purely
95
export function accessApp<A>(
96
f: (app: EX.Express) => A
97
): T.Effect<HasExpress & Express, T.NoErr, A>
98
99
// access request
100
export function accessReqM<R, E, A>(
101
f: (req: EX.Request) => T.Effect<R, E, A>
102
): T.Effect<RequestContext & Express & R, E, A>
103
104
// access request purely
105
export function accessReq<A>(
106
f: (req: EX.Request) => A
107
): T.Effect<RequestContext & Express, never, A>
108
109
// environment for consumer usage
110
export type ExpressEnv = HasExpress & Express;
111
112
// environment for consumer usage in request
113
export type ChildEnv = ExpressEnv & RequestContext;
114
115
// implementation
116
export const express: Express
Copied!

Usage

1
import { effect as T, exit as E } from "@matechs/effect";
2
import * as EX from "@matechs/express";
3
import { Do } from "fp-ts-contrib/lib/Do";
4
import { pipe } from "fp-ts/lib/pipeable";
5
6
// create a new express server
7
const program = EX.withApp(
8
Do(T.effect)
9
.do(
10
// bind GET / to {"message":"OK"}
11
EX.route(
12
"get",
13
"/",
14
T.pure(
15
EX.routeResponse(200, {
16
message: "OK"
17
})
18
)
19
)
20
)
21
.bind("server", EX.bind(8081)) // listen on port 8081
22
.return(s => s.server) // return node server
23
);
24
25
// construct live environment
26
const envLive = pipe(T.noEnv, T.mergeEnv(EX.express));
27
28
// run express server
29
T.run(
30
T.provideAll(envLive)(program),
31
E.fold(
32
server => {
33
// listen for exit Ctrl+C
34
process.on("SIGINT", () => {
35
server.close(err => {
36
process.exit(err ? 2 : 0);
37
});
38
});
39
40
// listen for SIGTERM
41
process.on("SIGTERM", () => {
42
server.close(err => {
43
process.exit(err ? 2 : 0);
44
});
45
});
46
},
47
e => console.error(e),
48
e => console.error(e),
49
() => console.error("interrupted")
50
)
51
);
Copied!
1
$ curl http://127.0.0.1:8081/
2
{"message":"OK"}
Copied!

Notes

The module is a work in progress and API is expected to be changed (not significantly).
Last modified 1yr ago