2.0.2 • Published 3 months ago

binden v2.0.2

Weekly downloads
-
License
AGPL-3.0-only
Repository
github
Last release
3 months ago

Binden CI Status version Known Vulnerabilities Coverage Status code style: prettier Contributor Covenant semantic-release Conventional Commits GitHub top language node version npm downloads License

A simple server framework (written in TypeScript).

Installation

npm install binden

Usage

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 });

Context

const { log } = context;
log.info("Hello World", { data: 100 });
  • .setHeader() - Set a response header
const name = "X-HEADER";
const value = ["value1", "value2"];
context.setHeader(name, value);
  • .status() - set the response status
context.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 object
const {
  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 });

Middleware

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

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` });
  }
}

BindenRequest

Simple usage with http

import { createServer } from "http";
import { BindenRequest } from "binden";
server = createServer({ IncomingMessage: BindenRequest });
  • .header() - get a header by name
const rawHeader = request.header("X-Header");
  • .id - get the request id
const { id } = request;
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)) };

BindenResponse

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 headers
const headers = {
  "X-AMOUNT": "100.02 USD",
  "X-MONTHS": ["jan", "feb"],
};
await response.status(402).set(headers).send("Payment is required");
  • .send() - send data
await 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);
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);

Headers

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;

Test

npm run test:ci
2.0.2

3 months ago

2.0.1

3 months ago

2.0.0

3 months ago

1.2.5

4 months ago

1.2.4

5 months ago

1.2.3

8 months ago

1.2.2

8 months ago

1.2.1

12 months ago

1.2.0

1 year ago

1.1.7

1 year ago

1.1.6

1 year ago

1.1.5

1 year ago

1.1.4

2 years ago

1.1.3

2 years ago

1.1.2

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.0

2 years ago