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

@maroonedsoftware/koa

v1.17.7

Published

Koa middleware, body parsing, and utilities for ServerKit

Readme

@maroonedsoftware/koa

Koa utilities and middleware for ServerKit: typed context, router, CORS, error handling, rate limiting, authentication, body parsing, and request-scoped DI via injectkit.

Installation

pnpm add @maroonedsoftware/koa koa @koa/router @koa/cors

Peer dependencies: koa, @koa/router, @koa/cors.

Features

  • ServerKitContext — Koa context extended with container, logger, requestId, correlationId, authenticationContext, and related request metadata
  • ServerKitRouter — Router typed for ServerKitContext
  • ServerKitMiddleware — Middleware type bound to ServerKitContext
  • serverKitContextMiddleware — Populates context with scoped container, logger, and request/correlation IDs; registers the live context against the ServerKitContext injection token so request-scoped services can inject it
  • corsMiddleware — CORS headers with '*', string, or RegExp origin matching
  • errorMiddleware — Central error handler; maps HTTP errors to status/body, 404 for unmatched routes, 500 for unknown errors
  • rateLimiterMiddleware — Per-IP rate limiting via rate-limiter-flexible (429 when exceeded)
  • authenticationMiddleware — Resolves the Authorization header via AuthenticationSchemeHandler and populates ctx.authenticationContext
  • bodyParserMiddleware — Parses JSON, form, text, multipart, or raw body by allowed content types
  • defaultParserMappings — Pre-built MIME-type-to-parser map for use with bodyParserMiddleware
  • requireSignature — Router middleware that verifies a request HMAC signature against ctx.rawBody
  • requireSecurity — Router middleware that enforces authentication and optional role-based authorization

Usage

Basic setup

import Koa from 'koa';
import { InjectKitRegistry } from 'injectkit';
import { Logger, ConsoleLogger } from '@maroonedsoftware/logger';
import {
  ServerKitRouter,
  serverKitContextMiddleware,
  corsMiddleware,
  errorMiddleware,
  bodyParserMiddleware,
} from '@maroonedsoftware/koa';

const diRegistry = new InjectKitRegistry();
diRegistry.register(Logger).useClass(ConsoleLogger).asSingleton();
const container = diRegistry.build();

const app = new Koa();
const router = new ServerKitRouter();

app.use(errorMiddleware());
app.use(serverKitContextMiddleware(container));
app.use(corsMiddleware({ origin: ['*'] }));

router.post('/api/echo', bodyParserMiddleware(['application/json']), async ctx => {
  ctx.body = { echoed: ctx.body, requestId: ctx.requestId };
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);

Route handlers with ServerKitContext

Handlers receive ctx as ServerKitContext with ctx.container, ctx.logger, ctx.requestId, ctx.correlationId, and ctx.userAgent:

router.get('/api/users/:id', async ctx => {
  ctx.logger.info('Fetching user', { id: ctx.params.id });
  const user = await ctx.container.get(UserService).findById(ctx.params.id);
  if (!user) throw httpError(404);
  ctx.body = user;
});

Injecting the context

ServerKitContext is also exported as an injectkit token. After serverKitContextMiddleware runs, the live ctx is registered against this token in the request-scoped container, so services resolved from ctx.container can declare it as a dependency:

import { Injectable } from 'injectkit';
import { ServerKitContext } from '@maroonedsoftware/koa';

@Injectable()
class CurrentUserService {
  constructor(private readonly ctx: ServerKitContext) {}

  get actorId() {
    return this.ctx.authenticationContext?.actorId;
  }
}

Authentication

authenticationMiddleware reads the Authorization header, delegates resolution to the AuthenticationSchemeHandler registered in the DI container, and populates ctx.authenticationContext. The header is deleted from ctx.req.headers immediately after reading so it cannot be captured by downstream logging.

import {
  AuthenticationSchemeHandler,
  AuthenticationHandlerMap,
} from '@maroonedsoftware/authentication';
import { authenticationMiddleware } from '@maroonedsoftware/koa';

// Register your scheme handlers in DI
diRegistry
  .register(AuthenticationHandlerMap)
  .useMap()
  .add('Bearer', BearerAuthHandler);

diRegistry.register(AuthenticationSchemeHandler).asSingleton();

// Add to the middleware stack after serverKitContextMiddleware
app.use(serverKitContextMiddleware(container));
app.use(authenticationMiddleware());

// Access the resolved context in route handlers
router.get('/api/me', async ctx => {
  const { actorId } = ctx.authenticationContext;
  ctx.body = { actorId };
});

Authorization

requireSecurity is router middleware that runs after authenticationMiddleware. It throws 401 when the request is unauthenticated and, if roles is provided, throws 403 unless the authenticated context has at least one of the listed roles.

import { requireSecurity } from '@maroonedsoftware/koa';

// Require any authenticated user
router.get('/api/profile', requireSecurity(), handler);

// Require at least one of the given roles
router.delete('/api/users/:id', requireSecurity({ roles: ['admin'] }), handler);

CORS

// Allow all origins
app.use(corsMiddleware({ origin: ['*'] }));

// Single origin
app.use(corsMiddleware({ origin: ['https://app.example.com'] }));

// Multiple origins or RegExps
app.use(
  corsMiddleware({
    origin: ['https://app.example.com', /^https:\/\/.*\.example\.com$/],
  }),
);

Rate limiting

import { RateLimiterMemory } from 'rate-limiter-flexible';
import { rateLimiterMiddleware } from '@maroonedsoftware/koa';

const rateLimiter = new RateLimiterMemory({
  points: 100,
  duration: 60,
});
app.use(rateLimiterMiddleware(rateLimiter));

Body parser

Allow specific content types; empty array disallows any body. Supports JSON, urlencoded, text, multipart, and raw (e.g. PDF).

router.post('/api/upload', bodyParserMiddleware(['multipart/form-data']), async ctx => {
  const body = ctx.body as MultipartBody;
  // ...
});

router.post('/api/json', bodyParserMiddleware(['application/json']), async ctx => {
  const data = ctx.body as Record<string, unknown>;
  // ...
});

Signature verification

requireSignature validates that an incoming request was signed with a shared secret. It computes an HMAC over ctx.rawBody and compares it to a header value. Use it for webhook endpoints from GitHub, Stripe, and similar services.

Store the options under a key in AppConfig and reference that key when adding the middleware:

config.json:

{
  "webhook": {
    "header": "X-Hub-Signature-256",
    "secret": "${env:WEBHOOK_SECRET}",
    "algorithm": "sha256",
    "digest": "hex"
  }
}
import { requireSignature, bodyParserMiddleware } from '@maroonedsoftware/koa';

// bodyParserMiddleware must run first so that ctx.rawBody is populated
router.post(
  '/webhooks/github',
  bodyParserMiddleware(['application/json']),
  requireSignature('webhook'),
  async ctx => {
    ctx.status = 204;
  },
);

Custom parser mappings

defaultParserMappings is the built-in MIME-type-to-parser map used by bodyParserMiddleware. You can extend or replace it to register additional parsers:

import {
  defaultParserMappings,
  BinaryParser,
  ServerKitParserMappings,
} from '@maroonedsoftware/koa';

const customMappings = {
  ...defaultParserMappings,
  pdf: BinaryParser,
};

// Register the mappings in the DI container; ServerKitBodyParser will resolve them.
const builder = diRegistry.register(ServerKitParserMappings).useMap();
for (const [mimeType, parser] of Object.entries(customMappings)) {
  builder.add(mimeType, parser);
}

The default mappings are:

| MIME subtype | Parser | | -------------------- | ----------------- | | json | JsonParser | | application/*+json | JsonParser | | urlencoded | FormParser | | text | TextParser | | multipart | MultipartParser |

BinaryParser is exported but not registered in defaultParserMappings; add it explicitly to handle raw payloads such as PDFs or images.

API

ServerKitContext

| Property | Type | Description | | ----------------------- | ----------------------- | ------------------------------------------------------ | | container | Container | Request-scoped injectkit container | | logger | Logger | Request-scoped logger | | loggerName | string | Logger name (e.g. request path) | | userAgent | string | User-Agent header value | | ipAddress | string | IP address of the client | | correlationId | string | From X-Correlation-Id header or generated | | requestId | string | From X-Request-Id header or generated | | rawBody | BinaryLike | Raw request body bytes; set by bodyParserMiddleware | | authenticationContext | AuthenticationContext | Resolved authentication context; set by authenticationMiddleware |

Middleware

| Middleware | Description | | --------------------------------------- | -------------------------------------------------------------------------------------------------- | | serverKitContextMiddleware(container) | Sets ctx.container, ctx.logger, IDs; sets X-Correlation-Id, X-Request-Id response headers | | corsMiddleware(options?) | CORS via @koa/cors; origin: '*', string, or (string \| RegExp)[] | | errorMiddleware() | Catches errors, maps HTTP errors to status/body, 404/500, emits app events | | rateLimiterMiddleware(rateLimiter) | Consumes one token per request by IP; throws 429 when exceeded | | authenticationMiddleware() | Resolves Authorization header via AuthenticationSchemeHandler; populates ctx.authenticationContext | | bodyParserMiddleware(contentTypes) | Parses body by allowed MIME types; throws 400/411/415/422 on invalid input | | requireSignature(optionsKey) | Verifies HMAC of ctx.rawBody against a request header; throws 401 on mismatch | | requireSecurity(options?) | Throws 401 when unauthenticated; throws 403 when none of the options.roles are present |

Parser options

Parser options classes are registered with InjectKit and can be configured in the DI container:

| Class | Key options | | -------------------- | ---------------------------------------------- | | JsonParserOptions | strict, protoAction, reviver, encoding, limit | | FormParserOptions | allowDots, depth, parameterLimit, encoding, limit | | TextParserOptions | encoding, limit |

License

MIT