A simple server framework (written in TypeScript).
npm install binden
.use()
- Add a Middleware/Router
to the
stack
import { Binden } from "binden";
const app = new Binden().use(middleware1).use(router2);
app.use("/path", middleware2, router2);
app.use(new RegExp("path"), router3, middleware1);
const middleware3 = (context) => context.json({ message: "Hello World!" });
app.use("/path2", middleware3);
.off()
- remove a Middleware/Router
form
the stack
import { Binden } from "binden";
const app = new Binden()
.use("/path", middleware1)
.use(middleware2)
.off("/path", middleware1);
.createServer()
- create a server (HTTP)import { Binden } from "binden";
const app = new Binden()
.use(new RegExp("path"), middleware)
.use("/path2", router);
const server = app.createServer();
.createSecureServer()
- create a server (HTTPS)
import { Binden } from "binden";
const app = new Binden().use("/path", middleware).use("/path2", router);
const secureServer = app.createSecureServer({ key, cert });
.log
- get the logger (see
@binden/logger
)
const { log } = context;
log.info("Hello World", { data: 100 });
.setHeader()
- Set a response headerconst name = "X-HEADER";
const value = ["value1", "value2"];
context.setHeader(name, value);
.status()
- set the response statuscontext.status(401);
.request
- get the original request object (instanceof BindenRequest
)
const { request } = context;
.response
- get the original response object (instanceof BindenResponse
)
const { response } = context;
.id
- get id
of the context (generated
by the
randomUUID()
function and logged as trace_id
by the
context.log
)
const { id } = context;
.done
class MyMiddleware extends Middleware {
public run(context: Context): void {
context.done = true; // Stop passing the `context` to other middlewares
}
}
or with a function
const MyMiddleware = (context): void => {
context.done = true;
};
.url
- parsed URL
objectconst {
log,
url: { search },
} = context;
log.trace("url search string", { search });
.send()
- execute
context.response.send()
and set
context.done
to true
await context.send(data);
// or
await context.response.send(data);
context.done = true;
.json()
- execute
context.response.json()
and set
context.done
to true
const json = { message: "Hello World!" };
await context.json(json);
// or
await context.response.json(json);
context.done = true;
A custom stringify
function (e.g.
fast-json-stringify) can pan passed as the second argument.
const json = { currency: "💶", value: 120 };
const fastJSON = await import("fast-json-stringify");
const stringify = fastJSON({
title: "Example Schema",
type: "object",
properties: {
currency: {
type: "string",
},
value: {
type: "integer",
},
},
required: ["currency", "value"],
additionalProperties: false,
});
const json = { currency: "💶", value: 120 };
await context.json(json, stringify);
// or using `BindenResponse`
await context.response.json(json, stringify);
context.done = true;
.text()
- execute
context.response.text()
and set
context.done
to true
const text = "Hello World!";
await context.text(text);
// or
await context.response.text(text);
context.done = true;
.html()
- execute
context.response.html()
and set
context.done
to true
const html = "<html></html>";
await context.html(html);
// or
await context.response.html(html);
context.done = true;
.form()
- execute
context.response.form()
and set
context.done
to true
const form = new URLSearchParams({ a: "1" });
await context.form(form);
// or
await context.response.form(form);
context.done = true;
.sendFile()
- execute
context.response.sendFile()
and set
context.done
to true
const path = "<path to file>";
await context.sendFile(path);
// or
await context.response.sendFile(path);
context.done = true;
.throw()
- throw BindenError
context.throw(402, { json: { error: "Payment Required" }, expose: true });
Any middleware should be extended from the abstract
Middleware
class and implement the
.run()
method
import { randomInt } from "crypto";
import { Middleware, Context } from "binden";
export class MyMiddleware extends Middleware {
public async run(context: Context): Promise<void> {
const randomNumber = await new Promise((resolve, reject) => {
randomInt(1, 100, (error, n) => {
if (error) {
reject(error);
} else {
resolve(n);
}
});
});
if (randomNumber <= 50) {
context.throw(400, {
message: "Generated number is less than or equal to 50",
expose: true,
});
}
return context.json({ message: "Generated number is greater than 50" });
}
}
.disabled
- One can disable a middleware at any time
import { Middleware, Context } from "binden";
export class MyMiddleware1 extends Middleware {
public run(context: Context): Promise<void> {
return context.json({ message: "Hello World" });
}
}
const mm1 = new MyMiddleware1({ disabled: true });
export class MyMiddleware2 extends Middleware {
public async run(): Promise<void> {
// Disable `mm1` every hour
setInterval(
() => {
mm1.disabled = !mm1.disabled;
},
1000 * 60 * 60,
);
}
}
.ignore_errors
- ignore errors from
await this.run(context)
import { Middleware, Context } from "binden";
export class MyMiddleware1 extends Middleware {
public run(context: Context): Promise<void> {
if (this.ignore_errors) {
return context.json({ message: "Hello World" });
}
context.throw(400);
}
}
const mm1 = new MyMiddleware1({ ignore_errors: true });
export class MyMiddleware2 extends Middleware {
public async run(): Promise<void> {
// Throw errors from `mm1.run()` every minute
setInterval(() => {
mm1.ignore_errors = !mm1.ignore_errors;
}, 1000 * 60);
}
}
BindenError
represents an HTTP error
import { Middleware, Context, BindenError } from "binden";
export class MyMiddleware extends Middleware {
public run(context: Context): Promise<void> {
const { length } = context.request.cookies;
if (!length) {
const status = 401;
const expose = true;
const message =
"Text message to send (when `expose === true` and `json === null`)";
const json = {
error:
"Send `json` as application/json (when `expose === true`) instead of `message`",
};
throw new BindenError(status, { expose, message, json });
}
try {
await validateBody();
} catch (cause) {
const message = "Invalid body";
const expose = true;
throw new BindenError(400, { expose, message, json: { message }, cause });
}
return context.json({ message: `Received ${length} cookies` });
}
}
Simple usage with http
import { createServer } from "http";
import { BindenRequest } from "binden";
server = createServer({ IncomingMessage: BindenRequest });
.header()
- get a header by nameconst rawHeader = request.header("X-Header");
.id
- get the request idconst { id } = request;
.protocol
- respects the
Forwarded
header:
const { protocol } = request;
if (protocol !== "https:") {
console.error("The connection is not secure");
}
.secure
- same as
request.protocol === "https:"
const { secure } = request;
if (!secure) {
console.error("The connection is not secure");
}
.query
- A copy of URL.search
parsed by
the querystring.parse()
method
const { query } = request;
// same as
const query = { ...parse(this.URL.search.substring(1)) };
Simple usage with http
import { createServer } from "http";
import { BindenResponse } from "binden";
server = createServer({ ServerResponse: BindenResponse });
.cookies
- The .send()
method will add
cookies to the response
import { randomUUID } from "crypto";
import { Cookie } from "binden";
const key = "__Secure-Random-UUID";
const value = randomUUID();
const cookie = new Cookie({ key, value });
response.cookies.add(cookie);
await response.send("Check the `Set-Cookie` header for a random UUID");
.status()
- Set the status code of the response
await response.status(400).send();
.set()
- Set the headersconst headers = {
"X-AMOUNT": "100.02 USD",
"X-MONTHS": ["jan", "feb"],
};
await response.status(402).set(headers).send("Payment is required");
.send()
- send dataawait response.send(
"Could be `number` | `string` | `Buffer` | `Readable` | `bigint` | `undefined`",
);
.json()
- send an object as
application/json
using JSON.stringify()
const json = { k: "v", k1: 1, m: "message", f: false };
await response.json(json);
or using a custom stringify
function
const json = { currency: "💶", value: 120 };
const fastJSON = await import("fast-json-stringify");
const stringify = fastJSON({
title: "Example Schema",
type: "object",
properties: {
currency: {
type: "string",
},
value: {
type: "integer",
},
},
required: ["currency", "value"],
additionalProperties: false,
});
await response.json(json);
.text()
- send text as plain/text
const text = "Hello World!";
await response.text(text);
.html()
- send text as text/html
const html = "<html></html>";
await response.html(html);
.form()
- send URLSearchParams
;const form = new URLSearchParams({ a: "1", b: ["a", "c"] });
await response.form(form);
.sendFile()
- Send a file. Respects the following
headers
const path = "<path to file>";
await response.sendFile(path);
// Or with custom Stats
import { stat } from "node:fs/promises";
const stats = await stat("<PATH>");
await response.sendFile(path, stats);
import { AcceptEncoding } from "binden";
const encodings = AcceptEncoding.fromString(request.headers["accept-encoding"]);
// or using BindenRequest
const { accept_encoding } = request;
import { Authorization } from "binden";
const authorization = AcceptEncoding.fromString(
request.headers["Authorization"],
);
// or using BindenRequest
const { authorization } = request;
import { ContentEncoding } from "binden";
const encodings = ContentEncoding.fromString(
request.headers["content-encoding"],
);
// or using BindenRequest
const { content_encoding } = request;
import { ContentRange } from "binden";
const cr = new ContentRange({ start: 0, end: 499, size: 1000 });
response.setHeader("Content-Range", cr.toString());
import { ContentType } from "binden";
const type = ContentType.fromString(request.headers["content-type"]);
// or using BindenRequest
const { content_type } = request;
import { Cookie } from "binden";
const cookies = Cookie.fromString(request.headers["cookie"]);
// or using BindenRequest
const { cookies } = request;
// or using BindenResponse
const cookie1 = new Cookie({
key: "__Secure-K1",
value: "v1",
http_only: false,
});
const cookie2 = new Cookie({
key: "K2",
value: "v2",
same_site: "None",
max_age: 1000,
});
response.cookies.add(cookie1).add(cookie2);
import { Forwarded } from "binden";
const forwarded = Forwarded.fromString(request.headers["forwarded"]);
// or using BindenRequest
const { forwarded } = request;
import { IfModifiedSince } from "binden";
const if_modified_since = IfModifiedSince.fromString(
request.headers["if-modified-since"],
);
// or using BindenRequest
const { if_modified_since } = request;
import { Range } from "binden";
const range = Range.fromString(request.headers.range);
// or using BindenRequest
const { range } = request;
npm run test:ci