{"id":17122,"library":"throwback","title":"Throwback Async Middleware Composer","description":"Throwback is a lightweight JavaScript/TypeScript library that implements a simple asynchronous middleware pattern, designed to compose promise-returning functions. It is currently in its stable version 4.1.0 and maintains an active release cadence, frequently introducing breaking changes in major versions to refine its API. Key differentiators include its strict focus on a single `ctx` argument for improved TypeScript inference (since v3.0.0), its reliance solely on native Promises (since v2.0.0), and its inspiration from Koa's middleware composition, aiming for a minimalistic yet powerful approach for processing pipelines. It is commonly used in server-side HTTP applications (e.g., Servie) and client-side request libraries (e.g., Popsicle), providing development-time debugging aids that are automatically excluded from production builds.","status":"active","version":"4.1.0","language":"javascript","source_language":"en","source_url":"git://github.com/serviejs/throwback","tags":["javascript","middleware","async","compose","promise","ware","layer","typescript"],"install":[{"cmd":"npm install throwback","lang":"bash","label":"npm"},{"cmd":"yarn add throwback","lang":"bash","label":"yarn"},{"cmd":"pnpm add throwback","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"While CommonJS `require` is shown in old examples, ESM `import` is the idiomatic way for modern TypeScript/JavaScript projects. Throwback ships with TypeScript types.","wrong":"const { compose } = require('throwback');","symbol":"compose","correct":"import { compose } from 'throwback';"},{"note":"Import the `MiddlewareFunction` type for strongly typing your middleware functions in TypeScript.","symbol":"MiddlewareFunction","correct":"import type { MiddlewareFunction } from 'throwback';"},{"note":"Import the `NextFunction` type for strongly typing the `next` argument in your middleware functions in TypeScript.","symbol":"NextFunction","correct":"import type { NextFunction } from 'throwback';"}],"quickstart":{"code":"import { compose } from 'throwback';\n\nconst middlewareStack = [\n  async function firstMiddleware(ctx, next) {\n    console.log('1: Before next()');\n    // 'next()' calls the next middleware in the stack or the final callback.\n    await next();\n    console.log('4: After next()');\n  },\n  async function secondMiddleware(ctx, next) {\n    console.log('2: Calling next()');\n    // You can optionally pass a new context to 'next()' which will replace it\n    // for subsequent middleware in the stack and the final callback (since v3).\n    // This is useful for request retries or modifying the context downstream.\n    await next();\n    console.log('3: Returning from next()');\n  }\n];\n\nconst composedApp = compose(middlewareStack);\n\n// The composed function takes a context object and an optional final callback.\n// The final callback runs at the end of the middleware stack, before the\n// execution bubbles back up through the middleware.\ncomposedApp({}, function finalCallback(ctx) {\n  console.log('5: Final callback executed. Context:', ctx);\n  ctx.status = 200;\n});\n\n/* Expected output:\n1: Before next()\n2: Calling next()\n5: Final callback executed. Context: { status: 200 }\n3: Returning from next()\n4: After next()\n*/","lang":"typescript","description":"Demonstrates how to compose asynchronous middleware functions and execute a stack with a context object and a final callback, showing the order of execution."},"warnings":[{"fix":"Remove any explicit `debugMode` configurations. Ensure your `NODE_ENV` is correctly set for development or production environments.","message":"The `debugMode` configuration option was removed. Debugging behavior is now solely controlled by `NODE_ENV`. Setting `NODE_ENV !== 'production'` enables debug checks and verbose errors.","severity":"breaking","affected_versions":">=4.1.0"},{"fix":"Instead of `await next(modifiedCtx);`, call `await next();` and modify `ctx` directly. For deep changes, consider a wrapper like `(fn) => (ctx, next) => fn(ctx.clone(), next)` if your context supports cloning.","message":"The ability to call `next(ctx)` directly to pass a modified context down the stack has been disabled. `next()` no longer accepts an argument. If you need to modify the context for subsequent middleware, it's recommended to create a wrapper function.","severity":"breaking","affected_versions":">=4.0.0"},{"fix":"Update your middleware functions to accept a single `ctx` argument. The `next` function is still accessible within the composed context's scope or by wrapping. Note: The `next()` function *can* accept an optional `ctx` argument to replace the context downstream since v3.0.0, but this was removed in v4.0.0.","message":"Middleware functions now accept only a single `ctx` argument. The previous pattern of `(ctx, next)` is no longer supported for the middleware function signature itself, though the `next` function is still passed as a second argument to the wrapper.","severity":"breaking","affected_versions":">=3.0.0 <4.0.0"},{"fix":"Ensure your runtime environment (Node.js or browser) provides a native `Promise` implementation. If targeting older environments, you must provide your own Promise polyfill globally before `throwback` is loaded.","message":"The library no longer uses `any-promise` and relies exclusively on the native global `Promise` constructor. This might break in environments without native Promise support or if `any-promise` was used for specific polyfills.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Pay attention to development-time errors to ensure correct middleware behavior. Do not rely on these error checks being present in production.","message":"In development (`NODE_ENV !== 'production'`), `compose` will throw specific errors for unexpected behavior (e.g., calling `next` multiple times). These checks are stripped in production builds for performance.","severity":"gotcha","affected_versions":">=3.0.0"}],"env_vars":null,"last_verified":"2026-04-22T00:00:00.000Z","next_check":"2026-07-21T00:00:00.000Z","problems":[{"fix":"Ensure your middleware signature correctly receives `next` and that you are not passing an argument to `next()` if using version 4.0.0 or higher. For versions < 4.0.0, `next(ctx)` was allowed.","cause":"Attempting to call `next()` when it's not correctly provided to the middleware or trying to pass `ctx` to `next()` after v4.0.0 which disabled this pattern.","error":"TypeError: next is not a function"},{"fix":"Ensure all functions in the middleware array are `async` functions or explicitly return a `Promise`. Example: `async function myMiddleware(ctx, next) { /* ... */ await next(); }`","cause":"A middleware function passed to `compose` is not an `async` function and does not explicitly return a Promise.","error":"Middleware function must be a generator or return a Promise"},{"fix":"Review your middleware logic. Each middleware function should call `await next()` at most once. If you need to short-circuit, simply omit calling `next()`.","cause":"A middleware function called `next()` more than once, which is an anti-pattern for this type of middleware flow and triggers a development-mode error.","error":"Error: next() called multiple times"},{"fix":"The `ctx` object is user-defined. If you need cloning functionality, you must implement it within your custom context object. The suggestion in the `throwback` documentation is a pattern recommendation, not an implicit feature of `throwback` itself.","cause":"Attempting to use `ctx.clone()` as suggested in some `throwback` examples, but your specific `ctx` object does not have a `clone` method.","error":"TypeError: ctx.clone is not a function (or similar context-related error)"}],"ecosystem":"npm","meta_description":null}