{"id":24083298,"url":"https://github.com/mitranim/koa-ring","last_synced_at":"2025-08-03T22:35:03.418Z","repository":{"id":82132054,"uuid":"99513980","full_name":"mitranim/koa-ring","owner":"mitranim","description":"Ring-style handlers for Koa: ƒ(request) → response. Supports automatic cancelation on client disconnect","archived":false,"fork":false,"pushed_at":"2018-07-01T08:41:56.000Z","size":40,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-03T21:55:44.021Z","etag":null,"topics":["cancelation","coroutines","futures","koa","middleware","ring","routing"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mitranim.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-08-06T21:21:19.000Z","updated_at":"2019-07-13T19:24:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"2ca1aad7-c784-495f-8b67-671e2eec47b1","html_url":"https://github.com/mitranim/koa-ring","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/mitranim/koa-ring","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fkoa-ring","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fkoa-ring/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fkoa-ring/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fkoa-ring/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mitranim","download_url":"https://codeload.github.com/mitranim/koa-ring/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fkoa-ring/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268623793,"owners_count":24280144,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-03T02:00:12.545Z","response_time":2577,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cancelation","coroutines","futures","koa","middleware","ring","routing"],"created_at":"2025-01-09T23:56:18.592Z","updated_at":"2025-08-03T22:35:03.404Z","avatar_url":"https://github.com/mitranim.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Overview\n\nAdapter library for [Koa](http://koajs.com), a popular HTTP microframework for Node.js. Allows you to write Koa handlers as `ƒ(request) -\u003e response`, similar to [Ring](https://github.com/ring-clojure/ring) in Clojure. See [motivation](#functional-programming).\n\nIncludes optional support for implicit cancelation via [Posterus](https://github.com/Mitranim/posterus) futures and coroutines/fibers. See [motivation](#cancelation).\n\n## TOC\n\n  * [Overview](#overview)\n  * [TOC](#toc)\n  * [Usage](#usage)\n  * [Motivation](#motivation)\n  * [API](#api)\n    * [Request](#request)\n    * [Response](#response)\n    * [`toKoaMiddleware`](#tokoamiddleware)\n  * [Futures](#futures)\n  * [Routing](#routing)\n  * [Changelog](#changelog)\n  * [Misc](#misc)\n\n## Usage\n\nShell:\n\n```sh\nnpm install --exact koa-ring\n```\n\nNode:\n\n```js\nconst Koa = require('koa')\nconst {toKoaMiddleware} = require('koa-ring')\n\nconst app = new Koa()\n\napp.use(toKoaMiddleware(exampleMiddleware(exampleHandler)))\n\nfunction exampleMiddleware(nextHandler) {\n  return async function prevHandler(request) {\n    // do whatever\n    // can substitute request\n    const req = patch(request, {})\n    const response = await nextHandler(req)\n    // can substitute response\n    return response || {status: 404}\n  }\n}\n\nfunction exampleHandler(request) {\n  // Status and headers are optional\n  return {status: 200, headers: {}, body: 'Hello world!'}\n}\n\nfunction patch(left, right) {\n  return Object.assign({}, left, right)\n}\n\nconst PORT = 9756\n\napp.listen(PORT, err =\u003e {\n  if (err) throw err\n  else console.info(`Server listening on port ${PORT}`)\n})\n```\n\nWith cancelation support:\n\n```js\nconst Koa = require('koa')\nconst {toKoaMiddleware} = require('koa-ring/posterus')\nconst {Future} = require('posterus')\n\nconst app = new Koa()\n\napp.use(toKoaMiddleware(handler))\n\n// Implicitly converted to a Posterus fiber by koa-ring\nfunction* handler(request) {\n  // Could be a future-based database request, etc\n  // This work can be automatically canceled if client disconnects\n  // koa-ring automatically calls future.deinit()\n  const greeting = yield Future.fromResult('Hello world!')\n  return {body: greeting}\n}\n\nconst PORT = 9756\n\napp.listen(PORT, err =\u003e {\n  if (err) throw err\n  else console.info(`Server listening on port ${PORT}`)\n})\n```\n\nSee [API](#api) below.\n\n## Motivation\n\n### Functional Programming\n\nIn Koa, request handlers take a request/response context object, return `void` and mutate the context to send the response. In other words, Koa is a poor match for the HTTP programming model, which lends itself to plain functions of `ƒ(request) -\u003e response`.\n\nAdvantages of `ƒ(request) -\u003e response`:\n\n  * Easy to rewrite request and/or response at handler level\n\n  * Lends itself to function composition\n\n  * You can often return response from another source, without writing a single line of context-mutating code\n\n  * Returning nothing instead of a response makes it easy to signal \"not found\" or \"noop\" to the calling handler\n\nFortunately, we can fix this. We have the technology to write functions.\n\n### Cancelation\n\nEach incoming request must keep track of the work it starts, and abort that work if the request ends prematurely. Promises lack this ability, and are therefore fundamentally broken and unfit for purpose.\n\nExamples from existing languages:\n\n  * In Erlang, you create subprocesses using `spawn_link`; they're owned by the master process and die along with it.\n\n  * In Go, you propagate cancelation using `context.Context`, supported by the standard library and many 3d party libraries.\n\n  * In thread-based languages, there's no analog of `spawn_link` or Go context. At best, the request-handling thread may be stopped when a request ends prematurely, but this doesn't propagate to any sub-threads spawned by it.\n\nIn Node.js, you can achieve this effect by using cancelable async primitives, such as [Posterus futures](https://github.com/Mitranim/posterus), and [coroutines](https://github.com/Mitranim/posterus#fiber) built on them.\n\nConcrete example:\n\n```js\nconst {Future} = require('posterus')\n\n// This is used internally by koa-ring\n// const {fiber} = require('posterus/fiber')\n\nfunction* koaRingHandler(request) {\n  // If client disconnects, this invokes onDeinit, aborting work\n  const one = yield expensiveFuture(request)\n  // Delegate to another fiber, implicitly owning it;\n  // if the client disconnects, both routines are canceled, aborting work\n  const other = yield expensiveRoutine(request)\n  return {body: other}\n}\n\n// Futures can be canceled with `future.deinit()`\nfunction expensiveFuture(...args) {\n  const future = new Future()\n  const operationId = expensiveOperation(...args, (error, result) =\u003e {\n    future.settle(error, result)\n  })\n  return future.finally(function finalize(error) {\n    if (error) cancelOperation(operationId)\n  })\n}\n\n// Routines are started as `const future = fiber(generatorFunction(...args))`\n// and canceled as `future.deinit()`\nfunction* expensiveRoutine(...args) {\n  const value = yield expensiveWork(...args)\n  return value\n}\n```\n\nLack of implicit cancelation leads to incorrect behavior. The client may wish to abort the work it has started; smart clients may cancel unnecessary requests to avoid wasting resources; and so on. Worse, this makes Node.js servers uniquely vulnerable to a certain type of DoS attack: making the server start expensive work and immediately canceling the request to free the attacker's system resources, while the server keeps slogging.\n\nFortunately, we can fix this. We have tools for implicit ownerhip and cancelation in async operations, such as [Posterus](https://github.com/Mitranim/posterus).\n\n## API\n\nIn Koa, every request handler acts as middleware: it controls the execution of the next handler, running code before and after it.\n\nIn `koa-ring`, these are separate concepts. A _middleware_ function creates a _request handler_ function by wrapping the next handler.\n\n```js\n// Response shape. Status and headers are optional\nconst mockResponse = {status: 200, headers: {}, body: 'Hello world!'}\n\nconst handler = request =\u003e mockResponse\n\nconst overwritingMiddleware = nextHandler =\u003e async request =\u003e {\n  const ignoredResponse = await nextHandler(request)\n  return mockResponse\n}\n\nconst noopMiddleware = nextHandler =\u003e nextHandler\n\nconst endware = () =\u003e handler\n```\n\nThe resulting handlers have a signature of `ƒ(request) -\u003e response` and lend themselves to composition and functional transformations of requests/responses.\n\n`koa-ring` doesn't provide any special tools for middleware. Wrap your handlers into middlewares before passing the final handler to [`toKoaMiddleware`](#tokoamiddleware) and then to `koa.use`.\n\n### Request\n\nEvery handler receives a request, which is a plain JS dict with the following shape:\n\n```js\ninterface Request {\n  url: string\n  location: Location\n  method: string\n  headers: {}\n  body: any\n  ctx: KoaContext\n  // Unimportant fields omitted\n}\n\ninterface Location {\n  pathname: string\n  search: string\n  query: {}\n  // Unimportant fields omitted\n}\n```\n\n`request.ctx` is the Koa context. It provides access to additional information and the underlying objects such as Node request, Node response, network socket, and so on. See the [Koa reference](http://koajs.com).\n\n`request.location` is the parsed version of `request.url`. It's very similar to a result of Node's `require('url').parse`, but with `location.query` parsed into a dict.\n\nUnlike Koa, `koa-ring` doesn't use prototype chains. The request is a plain JS dict. Middleware can pass modified copies:\n\n```js\nfunction mountingMiddleware(nextHandler) {\n  return function handler(request) {\n    const url = request.url.replace(/^\\/api(?=\\/)/, '')\n    return nextHandler(patch(request, {url}))\n  }\n}\n\nfunction patch(left, right) {\n  return Object.assign({}, left, right)\n}\n```\n\nYou can override both request and response:\n\n```js\nfunction middleware(handler) {\n  return async request =\u003e {\n    request = patch(request, {metadata: {}})\n    let response = await handler(request)\n    response = patch(response, {status: response.status || 200})\n    return response\n  }\n}\n\nfunction patch(left, right) {\n  return Object.assign({}, left, right)\n}\n```\n\n### Response\n\nHandlers return responses. A response is a plain JS dict with the following shape:\n\n```js\ninterface Response {\n  status: number\n  headers: {}\n  body: any\n}\n```\n\nEvery field is optional. It's ok to return nothing; `koa-ring` will just run the next Koa middleware.\n\nHandlers in a middleware can easily override each other's responses:\n\n```js\nconst middleware = next =\u003e async request =\u003e {\n  const response = await next(request)\n  return response || {status: 404}\n}\n```\n\n### `toKoaMiddleware`\n\nConverts a `koa-ring` handler into a Koa middleware. You should compose all your handlers and apply `koa-ring`-style middlewares before passing the resulting handler to `toKoaMiddleware`. You only need one per application.\n\n```js\nconst Koa = require('koa')\nconst {toKoaMiddleware} = require('koa-ring')\n\nconst app = new Koa()\n\n// Adds `request.body`\napp.use(require('koa-bodyparser')())\n\nconst echo = request =\u003e request\n\napp.use(toKoaMiddleware(echo))\n```\n\nSee below for the future-based version with cancelation.\n\n## Futures\n\nSee [motivation](#cancelation) for supporting futures.\n\nTo use `koa-ring` with Posterus futures and coroutines, use the optional `koa-ring/posterus` module.\n\n```js\nconst Koa = require('koa')\nconst {toKoaMiddleware} = require('koa-ring/posterus')\n\nconst app = new Koa()\n\napp.use(toKoaMiddleware(handler))\n\nfunction* handler(request) {\n  const response = yield someFuture(request)\n  return response\n}\n```\n\n## Routing\n\nBy preparsing `request.url` into `request.location`, `koa-ring` makes _manual_ routing much easier. You might not need a library:\n\n```js\nfunction mainHandler(request) {\n  const {location: {pathname}} = request\n  if (/^[/]api[/]/.test(pathname)) return apiHandler(request)\n  return viewHandler(request)\n}\n\nfunction apiHandler(request) {\n  const {method, location: {pathname}} = request\n\n  if (method === 'GET' \u0026\u0026 pathname === '/api/users') {\n    return userHandler(request)\n  }\n\n  if (method === 'POST' \u0026\u0026 pathname === '/api/login') {\n    return loginHandler(request)\n  }\n\n  // ...\n}\n```\n\n## Changelog\n\n### 0.3.2\n\nUpdated dependencies.\n\n### 0.3.1\n\nUpdated to a newer version of Posterus (bugfix).\n\n### 0.3.0\n\nBreaking: replaced routing utils with URL preparsing. Tentative.\n\n* removed `match`\n* removed `mount`\n* added `request.location`\n\nInstead of using multiple functions hidden behind routes, you're supposed to route imperatively, with a series of `if/else`, by looking at the conveniently-parsed `request.location`.\n\nThis is tentative, likely to be followed by more changes as I'm experimenting with the idea.\n\n## Misc\n\nI'm receptive to suggestions. If this library _almost_ satisfies you but needs changes, open an issue or chat me up. Contacts: https://mitranim.com/#contacts\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitranim%2Fkoa-ring","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmitranim%2Fkoa-ring","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitranim%2Fkoa-ring/lists"}