npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

binden

v2.0.3

Published

A simple HTTP server framework

Downloads

186

Readme

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