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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gorilli-team/bananaloop-catch

v0.3.0

Published

Lightweight Node.js error capture SDK with Express and Next.js integrations.

Readme

@gorilli-team/bananaloop-catch

Lightweight Node.js error capture SDK. Automatically reports unhandled exceptions and promise rejections, with built-in integrations for Express and Next.js.

Requirements

  • Node.js 18+
  • An ingest server URL and a per-project ingest token. Token format: bnl_<id>_<secret>.

Installation

npm install @gorilli-team/bananaloop-catch
# or
pnpm add @gorilli-team/bananaloop-catch
# or
yarn add @gorilli-team/bananaloop-catch

Quick Start

Call init() once at the entry point of your application, before any other code runs:

import { init } from '@gorilli-team/bananaloop-catch';

init({
  apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
});

The per-project token identifies the project and workspace server-side — no projectId config field is required. After this, all uncaught exceptions and unhandled promise rejections are automatically captured and sent to your ingest server.

Environment Variables

The SDK reads these environment variables for runtime context. Setting them is optional; you can also pass equivalent values via code.

| Variable | Description | |---|---| | BANANALOOP_GIT_SHA | Git commit SHA to attach to every error report. Falls back to git rev-parse --short HEAD. | | BANANALOOP_GIT_BRANCH | Git branch name to attach to every error report. Falls back to git rev-parse --abbrev-ref HEAD. | | BANANALOOP_TASK_ID | Task ID associated with the running process. | | BANANALOOP_WORKSPACE_ID | Workspace ID associated with the running process. | | BANANALOOP_DAEMON_ID | Daemon / runner ID associated with the running process. |

BANANALOOP_INGEST_TOKEN in the Quick Start is just an example name — the SDK does not read it automatically. Pass its value to init() via apiKey.

Configuration

init() accepts a CatchConfig object:

init({
  apiKey: 'bnl_...',                     // required — per-project ingest token
  apiUrl: 'https://ingest.example.com',  // optional — base URL of the ingest server
  // projectId: 'your-project-id',       // deprecated — only needed for legacy global keys
});

Deprecated: projectId is ignored when apiKey is a per-project token (bnl_*). It is only honored when apiKey is a legacy global ingest key, which will be removed in a future release.

Reports are POSTed to ${apiUrl}/api/errors/ingest with the apiKey as a bearer token.

API Reference

init(config: CatchConfig): void

Initialises the global Catch client and attaches uncaughtException / unhandledRejection process handlers. Safe to call multiple times — handlers are only attached once.

import { init } from '@gorilli-team/bananaloop-catch';

init({
  apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
});

Config options:

| Field | Type | Required | Description | |---|---|---|---| | apiKey | string | Yes | Per-project ingest token (bnl_<id>_<secret>). | | apiUrl | string | No | Base URL of the ingest server. Defaults to the built-in value. | | projectId | string | No | Legacy — only honored when apiKey is a global key. |


getContext(): RuntimeContext | null

Returns the RuntimeContext collected at init time, or null if init() has not been called.

import { getContext } from '@gorilli-team/bananaloop-catch';

const ctx = getContext();
console.log(ctx?.gitBranch); // e.g. "main"

RuntimeContext shape:

interface RuntimeContext {
  gitSha: string | null;
  gitBranch: string | null;
  hostname: string;
  nodeVersion: string;
  taskId: string | null;
  workspaceId: string | null;
  daemonId: string | null;
}

createCatchClient(config, context?): CatchClient

Creates a standalone CatchClient instance without touching global state. Use this when you need multiple clients or want to avoid global side-effects.

import { createCatchClient, collectContext } from '@gorilli-team/bananaloop-catch';

const client = createCatchClient(
  {
    apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
  },
  collectContext(),
);

await client.captureError(new Error('Something went wrong'));

CatchClient interface:

interface CatchClient {
  captureError(error: Error, context?: Record<string, unknown>): Promise<void>;
  captureException(error: unknown, context?: Record<string, unknown>): Promise<void>;
}
  • captureError — accepts an Error instance directly.
  • captureException — accepts any thrown value; non-Error values are converted via new Error(String(value)).

collectContext(): RuntimeContext

Reads environment variables and system info to build a RuntimeContext. Called internally by init().

import { collectContext } from '@gorilli-team/bananaloop-catch';

const ctx = collectContext();

detectGitSha(): string | null

Returns the current git commit SHA (short form). Prefers BANANALOOP_GIT_SHA env var, falls back to running git rev-parse --short HEAD. Returns null if neither is available.

import { detectGitSha } from '@gorilli-team/bananaloop-catch';

const sha = detectGitSha(); // e.g. "a1b2c3d"

detectGitBranch(): string | null

Returns the current git branch name. Prefers BANANALOOP_GIT_BRANCH env var, falls back to running git rev-parse --abbrev-ref HEAD. Returns null if neither is available.

import { detectGitBranch } from '@gorilli-team/bananaloop-catch';

const branch = detectGitBranch(); // e.g. "main"

createExpressErrorHandler(client: CatchClient): ErrorRequestHandler

Creates an Express 4-argument error-handling middleware that captures errors via a provided CatchClient instance. Always calls next(err) to continue the Express error chain.

import express from 'express';
import { createCatchClient, createExpressErrorHandler, collectContext } from '@gorilli-team/bananaloop-catch';

const app = express();
const client = createCatchClient(
  {
    apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
  },
  collectContext(),
);

app.use(createExpressErrorHandler(client));

captureExpressErrors(): ErrorRequestHandler

Returns Express error-handling middleware that uses the global client initialised by init(). Register as your last error middleware.

import express from 'express';
import { init, captureExpressErrors } from '@gorilli-team/bananaloop-catch';

init({
  apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
});

const app = express();

// ... your routes ...

app.use(captureExpressErrors()); // must be last

withCatch(client: CatchClient, handler): Handler

Wraps a Next.js App Router route handler. Exceptions are captured and re-thrown so Next.js can still handle its own error boundary / 500 response logic.

import { withCatch } from '@gorilli-team/bananaloop-catch';

captureNextErrors(err: unknown, req?: NextCompatibleRequest): void

Captures an error from a Next.js error page. Safe to call from any rendering context — never throws. Accepts both App Router (Request) and Pages Router (IncomingMessage) request objects.


Framework Integration Examples

Plain Node.js

import { init } from '@gorilli-team/bananaloop-catch';

init({
  apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
});

// Unhandled errors are now auto-captured.
// You can also capture manually:
import { createCatchClient, collectContext } from '@gorilli-team/bananaloop-catch';

const client = createCatchClient(
  {
    apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
  },
  collectContext(),
);

try {
  riskyOperation();
} catch (err) {
  await client.captureException(err, { userId: '123', action: 'riskyOperation' });
  throw err;
}

Express

import express from 'express';
import { init, captureExpressErrors } from '@gorilli-team/bananaloop-catch';

init({
  apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
});

const app = express();

app.get('/example', (req, res) => {
  res.json({ ok: true });
});

// Register error handler last — after all routes and other middleware.
app.use(captureExpressErrors());

app.listen(3000);

The middleware automatically attaches safe request metadata (URL, method, selected headers) to each captured error.

Next.js — App Router

instrumentation.ts (runs once at startup):

export async function register() {
  const { init } = await import('@gorilli-team/bananaloop-catch');
  init({
    apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
  });
}

Route handlers (app/api/example/route.ts):

import { createCatchClient, collectContext, withCatch } from '@gorilli-team/bananaloop-catch';
import { NextResponse } from 'next/server';

const client = createCatchClient(
  {
    apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
  },
  collectContext(),
);

export const GET = withCatch(client, async (_req) => {
  // errors thrown here are captured and re-thrown for Next.js to handle
  return NextResponse.json({ ok: true });
});

Error boundary (app/error.tsx):

'use client';

import { useEffect } from 'react';
import { captureNextErrors } from '@gorilli-team/bananaloop-catch';

export default function ErrorPage({ error }: { error: Error }) {
  useEffect(() => {
    captureNextErrors(error);
  }, [error]);

  return <div>Something went wrong.</div>;
}

Next.js — Pages Router

pages/_app.tsx:

import type { AppProps } from 'next/app';
import { init } from '@gorilli-team/bananaloop-catch';

init({
  apiKey: process.env.BANANALOOP_INGEST_TOKEN!,
});

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

pages/_error.tsx:

import type { NextPageContext } from 'next';
import { captureNextErrors } from '@gorilli-team/bananaloop-catch';

function ErrorPage({ statusCode }: { statusCode?: number }) {
  return <p>{statusCode} — An error occurred.</p>;
}

ErrorPage.getInitialProps = ({ res, err, req }: NextPageContext) => {
  if (err) captureNextErrors(err, req ?? undefined);
  return { statusCode: res?.statusCode ?? err?.statusCode ?? 404 };
};

export default ErrorPage;

Error Handling Best Practices

Always initialise before other code. Call init() at the top of your entry file so process-level handlers are attached before any imports throw.

Use captureException for caught errors you still want to re-throw. It normalises non-Error values automatically:

try {
  await thirdPartyCall();
} catch (err) {
  await client.captureException(err, { service: 'thirdParty' });
  throw err; // re-throw so callers know the operation failed
}

Add structured context to help with debugging:

await client.captureError(err, {
  userId: session.userId,
  requestId: req.headers['x-request-id'],
  operation: 'processPayment',
  orderId: order.id,
});

Never swallow errors silently. The SDK is fire-and-forget by design — network failures do not throw. Your application's own error handling (re-throwing, returning 500 responses) is still your responsibility.

Keep apiKey in environment variables. Never hardcode the ingest API key in source code.

Troubleshooting

Errors not reaching the ingest server

  1. Check that init() is called before any errors can occur.
  2. Verify apiKey is set and non-empty — the SDK logs a warning to console.warn if it is missing.
  3. Confirm your service can reach the ingest apiUrl (check firewall / egress rules).
  4. The SDK has a 5-second timeout per request. In serverless environments with very short execution windows, the ingest request may be killed before it completes. Use waitUntil or an equivalent mechanism if available.

[catch] Missing required config: apiKey

init() was called without a valid apiKey. Check that the ingest token is populated in your deployment environment.

[catch] Failed to send error to ingest: ...

A network error occurred while sending the report. The original error in your application is unaffected. Verify network connectivity and that apiUrl is correct.

[catch] Ingest request failed with status 401

The API key is invalid or has been revoked. Regenerate the key.

[catch] Ingest request failed with status 429

You are being rate limited. Reduce the volume of errors or contact your ingest provider.

Git context is null

Ensure the BANANALOOP_GIT_SHA / BANANALOOP_GIT_BRANCH environment variables are set in your CI/CD pipeline, or that a .git directory is present at runtime so the SDK can call git rev-parse.

TypeScript: module resolution errors

The package uses ESM ("type": "module"). Ensure your tsconfig.json uses "moduleResolution": "bundler" or "node16" / "nodenext" and that your build tool supports ESM.

TypeScript

The package ships with full TypeScript declarations. All types are exported from the main entry point:

import type { CatchConfig, CatchClient } from '@gorilli-team/bananaloop-catch';
import type { RuntimeContext } from '@gorilli-team/bananaloop-catch';
import type { NextCompatibleRequest } from '@gorilli-team/bananaloop-catch';