Gruber

Node.js

This module provides the integration between Gruber and Node.js, along with platform-specific utilitites and Gruber primatives.

There are also opt-in modules for specific integrations and a polyfill if you are using an older Node.js. You have to import these with specific paths, they are not included in the default export.

Install

Gruber is available through NPM for Node.js.

npm install gruber

Integrations

There are platform-specific integrations with the Configuration, Postgres & Terminator modules:

import postgres from "postgres";
import { getConfiguration, getPostgresMigrator, getTerminator } from "gruber";

// Get a Node.js specific Configuration instance that
// loads files using 'fs' and parses them through JSON
const config = getConfiguration();

// Get a Migrator using the Node.js filesystem and
// the postgres.js library
const migrator = getPostgresMigrator({
  sql: postgres("postgres://…"),
  directory: new URL("./migrations/", import.meta.url),
});

// Get a terminator that listens to Node.js' process signals
const arnie = getTerminator();

Utilities

There are some Node.js specific utilities too, to help with Gruber integration and web-standards.

import { serveHTTP } from "gruber";

// Create a node:http server, wrapped with the Fetch API Request/Response objects.
serveHTTP({ port: 3000 }, async (request) => {
  return Response.json({ message: "ok" });
});

Polyfil

// Import this as soon as possible to ensure
// the web-standards primatives Gruber expects are available.
import "gruber/polyfill.js";

Express

There is a middleware for using a FetchRouter with an Express application.

import express from "express"
import { FetchRouter } from "gruber";
import { expressMiddleware } from "gruber/express-router.js";

const router = new FetchRouter()
const app = express()
  .use()
  .use(expressMiddleware(router))
  .use()

Koa

There is a middleware for using a FetchRouter with a koa application.

import Koa from "koa"
import { FetchRouter } from "gruber";
import { koaMiddleware } from "gruber/koa-router.js";

const router = new FetchRouter()
const app = new Koa()
  .use()
  .use(koaMiddleware(router));
  .use()

Miscellaneous

getConfigurationOptions

Generate standardish options to create a Configuration from the Node.js environment that reads JSON files.

  • It uses parseArgs from node:util to parse CLI arguments
  • It uses promises.readFile from node:fs to read text files
  • It reads environment variables from node:process
  • It parses and stringifies configuration using JSON
const options = getConfigurationOptions()

getConfiguration

Create a standardish Node.js Configuration. It creates a new Configuration object using getConfigurationOptions.

const config = getConfiguration()

createStoppable

A port of stoppable.js, ported to Gruber to reduce external dependencies.

Adapted from stoppable.js

NodeRouter

A HTTP router for pure Node.js, you should probably use serveHTTP

import http from "node:http";

const router = new NodeRouter(...)
const server = http.createServer(router.forHttpServer())
server.listen(3000)

getPostgresMigratorOptions

Create a standardish Postgres Migrator based on the filesystem and an sql connection from postgres.js

const sql = postgres()

const migrator = getPostgresMigratorOptions({
	sql,
	directory: new URL("./migrations/", import.meta.url)
})

getPostgresMigrator

This is a syntax sugar for new Migrator(getPostgresMigratorOptions(...))

HTTP

applyResponse

Send a web-standards Response to a Node.js ServerResponse

import http from "node:http"

http.createServer((req, res) => {
	applyResponse(
		Response.json({ msg: "ok" }),
		res
	)
})

getFetchRequest

Convert a Node.js IncomingMessage into a web-standards Request

import http from "node:http"

http.createServer((req, res) => {
	let request = getFetchRequest(req)
	// ...
})

getFetchHeaders

Parse Node.js IncomingHttpHeaders into a web-standards Headers object

const headers = getFetchHeaders({ accept: "text/plain" }) // Headers

getIncomingMessageBody

Convert the body of a Node.js IncomingMessage into a Streams API ReadableStream

import http from "node:http"

http.createServer((req, res) => {
	let stream = getIncomingMessageBody(req)
	// ...
})

getResponseReadable

Convert a Streams API ReadableStream into a Readable to later be piped to a Node.js ServerResponse

import http from "node:http"

http.createServer((req, res) => {
	const webResponse = Response.json({ msg: "OK" })
	getResponseReadable(webResponse, res).pipe(res)
})

Pass the second, res parameter if you'd like to terminate the web Response if Node.js is terminated.

serveHTTP unstable

A simple abstraction for creating a HTTP server, converting Node.js primatives into Fetch API objects and handling requests through a FetchRouter.

const server = await serveHTTP({ port: 3000 }, async (request) => {
	return new Response('Hello, There!')
})

This method returns a node:http Server after waiting for it to start listening. The server has an extra stop method and also implements [Symbol.asyncDispose].

When stop is called, or it is dispoed with the using keyword, it will attempt to gracefully shutdown the HTTP server, attempting to terminate each connection. If you created the server with a grace option, it will wait for that maximum time before forcing every connection to close. To quote the author of stoppable, this is "the way you probably expected it to work by default".

async function main() {
	await using server = await serveHTTP(
		{ port: 3000, grace: 5000 },
		() => new Response('ok')
	)
}

await main()

When main function exits, it will automatically close the server with a 5 second grace period.

The stop method is also useful when used with a Terminator.

debug
{
  "getConfigurationOptions": {
    "entrypoint": "node/mod.ts",
    "id": "getConfigurationOptions",
    "name": "getConfigurationOptions",
    "content": "Generate standardish options to create a Configuration from the Node.js environment that reads JSON files.\n\n- It uses parseArgs from `node:util` to parse CLI arguments\n- It uses promises.readFile from `node:fs` to read text files\n- It reads environment variables from `node:process`\n- It parses and stringifies configuration using `JSON`\n\n```js\nconst options = getConfigurationOptions()\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "getConfiguration": {
    "entrypoint": "node/mod.ts",
    "id": "getConfiguration",
    "name": "getConfiguration",
    "content": "Create a standardish Node.js Configuration.\nIt creates a new Configuration object using [getConfigurationOptions](#getconfigurationoptions).\n\n```js\nconst config = getConfiguration()\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "applyResponse": {
    "entrypoint": "node/mod.ts",
    "id": "applyResponse",
    "name": "applyResponse",
    "content": "Send a web-standards Response to a Node.js [ServerResponse](https://nodejs.org/api/http.html#class-httpserverresponse)\n\n```js\nimport http from \"node:http\"\n\nhttp.createServer((req, res) => {\n\tapplyResponse(\n\t\tResponse.json({ msg: \"ok\" }),\n\t\tres\n\t)\n})\n```",
    "tags": {
      "group": "HTTP"
    },
    "children": {}
  },
  "getFetchRequest": {
    "entrypoint": "node/mod.ts",
    "id": "getFetchRequest",
    "name": "getFetchRequest",
    "content": "Convert a Node.js [IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage) into a\nweb-standards [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)\n\n```js\nimport http from \"node:http\"\n\nhttp.createServer((req, res) => {\n\tlet request = getFetchRequest(req)\n\t// ...\n})\n```",
    "tags": {
      "group": "HTTP"
    },
    "children": {}
  },
  "getFetchHeaders": {
    "entrypoint": "node/mod.ts",
    "id": "getFetchHeaders",
    "name": "getFetchHeaders",
    "content": "Parse Node.js IncomingHttpHeaders into a web-standards [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object\n\n```js\nconst headers = getFetchHeaders({ accept: \"text/plain\" }) // Headers\n```",
    "tags": {
      "group": "HTTP"
    },
    "children": {}
  },
  "getIncomingMessageBody": {
    "entrypoint": "node/mod.ts",
    "id": "getIncomingMessageBody",
    "name": "getIncomingMessageBody",
    "content": "Convert the body of a Node.js [IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage) into a Streams API ReadableStream\n\n```js\nimport http from \"node:http\"\n\nhttp.createServer((req, res) => {\n\tlet stream = getIncomingMessageBody(req)\n\t// ...\n})\n```",
    "tags": {
      "group": "HTTP"
    },
    "children": {}
  },
  "getResponseReadable": {
    "entrypoint": "node/mod.ts",
    "id": "getResponseReadable",
    "name": "getResponseReadable",
    "content": "Convert a Streams API ReadableStream into a Readable to later be piped to a Node.js [ServerResponse](https://nodejs.org/api/http.html#class-httpserverresponse)\n\n```js\nimport http from \"node:http\"\n\nhttp.createServer((req, res) => {\n\tconst webResponse = Response.json({ msg: \"OK\" })\n\tgetResponseReadable(webResponse, res).pipe(res)\n})\n```\n\nPass the second, `res` parameter if you'd like to terminate the web Response if Node.js is terminated.",
    "tags": {
      "group": "HTTP"
    },
    "children": {}
  },
  "serveHTTP": {
    "entrypoint": "node/mod.ts",
    "id": "serveHTTP",
    "name": "serveHTTP",
    "content": "A simple abstraction for creating a HTTP server, converting Node.js primatives into Fetch API objects and handling requests through a FetchRouter.\n\n```js\nconst server = await serveHTTP({ port: 3000 }, async (request) => {\n\treturn new Response('Hello, There!')\n})\n```\n\nThis method returns a `node:http` Server after waiting for it to start listening.\nThe server has an extra `stop` method and also implements `[Symbol.asyncDispose]`.\n\nWhen stop is called, or it is dispoed with the `using` keyword, it will attempt to gracefully shutdown the HTTP server,\nattempting to terminate each connection. If you created the server with a `grace` option,\nit will wait for that maximum time before forcing every connection to close.\nTo quote the author of stoppable, this is \"the way you probably expected it to work by default\".\n\n```js\nasync function main() {\n\tawait using server = await serveHTTP(\n\t\t{ port: 3000, grace: 5000 },\n\t\t() => new Response('ok')\n\t)\n}\n\nawait main()\n```\n\nWhen main function exits, it will automatically close the server with a 5 second grace period.\n\nThe stop method is also useful when used with a [Terminator](/core/#terminator).",
    "tags": {
      "unstable": "true",
      "group": "HTTP"
    },
    "children": {}
  },
  "createStoppable": {
    "entrypoint": "node/mod.ts",
    "id": "createStoppable",
    "name": "createStoppable",
    "content": "A port of stoppable.js, ported to Gruber to reduce external dependencies.\n\nAdapted from [stoppable.js](https://github.com/hunterloftis/stoppable/blob/master/lib/stoppable.js)",
    "tags": {
      "hidden": "true",
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "NodeRouter": {
    "entrypoint": "node/mod.ts",
    "id": "NodeRouter",
    "name": "NodeRouter",
    "content": "A HTTP router for pure Node.js, you should probably use [serveHTTP](#servehttp)\n\n```js\nimport http from \"node:http\";\n\nconst router = new NodeRouter(...)\nconst server = http.createServer(router.forHttpServer())\nserver.listen(3000)\n```",
    "tags": {
      "hidden": "true",
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "getPostgresMigratorOptions": {
    "entrypoint": "node/mod.ts",
    "id": "getPostgresMigratorOptions",
    "name": "getPostgresMigratorOptions",
    "content": "Create a standardish Postgres Migrator based on the filesystem and an sql connection from [postgres.js](https://github.com/porsager/postgres)\n\n```js\nconst sql = postgres(…)\n\nconst migrator = getPostgresMigratorOptions({\n\tsql,\n\tdirectory: new URL(\"./migrations/\", import.meta.url)\n})\n\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "getPostgresMigrator": {
    "entrypoint": "node/mod.ts",
    "id": "getPostgresMigrator",
    "name": "getPostgresMigrator",
    "content": "This is a syntax sugar for `new Migrator(getPostgresMigratorOptions(...))`",
    "tags": {
      "param": "{PostgresMigratorOptions} options",
      "group": "Miscellaneous"
    },
    "children": {}
  }
}