{"id":14848270,"url":"https://github.com/DavidJFelix/serverless-compose","last_synced_at":"2025-09-18T04:33:04.731Z","repository":{"id":39989588,"uuid":"494211222","full_name":"DavidJFelix/serverless-compose","owner":"DavidJFelix","description":null,"archived":true,"fork":false,"pushed_at":"2022-05-19T20:10:33.000Z","size":640,"stargazers_count":0,"open_issues_count":10,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-03T00:52:18.701Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DavidJFelix.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-05-19T19:55:07.000Z","updated_at":"2023-01-28T00:38:58.000Z","dependencies_parsed_at":"2022-09-19T00:51:09.343Z","dependency_job_id":null,"html_url":"https://github.com/DavidJFelix/serverless-compose","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidJFelix%2Fserverless-compose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidJFelix%2Fserverless-compose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidJFelix%2Fserverless-compose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidJFelix%2Fserverless-compose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DavidJFelix","download_url":"https://codeload.github.com/DavidJFelix/serverless-compose/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232741510,"owners_count":18569402,"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","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":[],"created_at":"2024-09-19T13:01:51.009Z","updated_at":"2025-09-18T04:32:59.417Z","avatar_url":"https://github.com/DavidJFelix.png","language":"TypeScript","funding_links":[],"categories":["Frameworks"],"sub_categories":[],"readme":"# serverless-compose\n\n[![npm version](https://img.shields.io/npm/v/serverless-compose.svg?logo=npm\u0026style=popout-square)](https://www.npmjs.com/package/serverless-compose)\n[![codecov coverage](https://img.shields.io/codecov/c/github/davidjfelix/serverless-compose.svg?logo=codecov\u0026style=popout-square)](https://codecov.io/gh/DavidJFelix/serverless-compose)\n\n_A lightweight functional middleware framework for AWS lambda that stays out of your way and lets you build kick-ass, composable middleware for your lambdas._\n\nZero dependencies. Functional interface. Reusable code.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Project Purpose](#project-purpose)\n- [Basic Usage](#basic-usage)\n- [Customizing Existing Middleware](#customizing-existing-middleware)\n- [Creating Partial Middleware](#creating-partial-middleware)\n- [Building New Middleware](#building-new-middleware)\n\n## Installation\n\nTo use `serverless-compose` in your nodejs project,\n\n```bash\nyarn add serverless-compose\n```\n\nor\n\n```bash\nnpm i serverless-compose\n```\n\n`serverless-compose` has no dependencies itself (peer or otherwise), so that's it!\n\n## Project Purpose\n\nThe goal of this project is to provide a very thin middleware framework for AWS lambda.\nWithout a well-established middleware pattern, too many bad habits can fossilize from one-off functions into bad middleware.\nThis library provides a `compose` function for wrapping middleware around a handler without having deeply nested code.\nThis function wrapping pattern allows explicitly definied, functional and onion-style (a well-established style) middleware.\n`serverless-compose` also builds on the basic compose function, offering a few patterns that the author(s) have seen in the wild for rapid customization.\n\nToo many middlework frameworks enforce bad designs and opinions onto their users.\n`serverless-compose` doesn't impose much at all and it stays out of your way once you define your stack.\n\n## Basic Usage\n\nThe most basic use of `serverless-compose` is to add timing and error middleware to your handlers.\n`recoveryMiddleware` adds code for rejected promises (or thrown `async` functions) in your handlers.\nThe following example wraps a rejection with an actual response so AWS API Gateway can handle and pass to clients, rather than sending its own 503 error with no information.\n\n```javascript\nimport {\n  compose,\n  recoveryMiddleware,\n  timingLogMiddleware,\n} from 'serverless-compose'\n\n// Suppose this is a client that fetches the weather from some external API\nimport { WeatherClient } from 'made-up-weather-library'\n\n// Your actual handler code\nasync function getWeather(request, context) {\n  // All this does is call this API, but suppose the API is bad and fails a lot,\n  // so this is likely to throw an error, rejecting the promise of this function\n  response = await WeatherClient.askBadAPIForWeather()\n}\n\n// Your own custom logging function\nasync function logDuration(duration) {\n  console.log(`It took: ${duration}ms to return the weather`)\n}\n\n// Your own custom error handler\nasync function sendError(error) {\n  return {\n    statusCode: 500,\n    body: JSON.stringify({\n      error: `${error}`,\n      message: 'The darn weather API failed me again!',\n    }),\n  }\n}\n\nconst TimingMiddleware = timingLogMiddleware(logDuration)\nconst RecoveryMiddleware = recoveryMiddleware(sendError)\nconst MyMiddlewareStack = compose(\n  TimingMiddleware,\n  RecoveryMiddleware,\n)\n\nexport const lambdaHandler = MyMiddlewareStack(getWeather)\n```\n\n## Customizing Existing Middleware\n\n`serverless-compose` provides a number of middleware patterns for users to customize and create their own middleware.\nThese patterns aren't extensive, but do provide a large variety of options.\nThe current API contains the following middleware options:\n\n### timingLogMiddleware\n\n```javascript\nimport { timingLogMiddleware } from 'serverless-compose'\n```\n\n`timinglogMiddlware` takes in a logging function of the signature `(duration, {event, result}) =\u003e void` and calls this function after the `next` handler has returned.\n\nThis middleware is useful for recording the time taken by a lambda in other means than the existing AWS lambda logging.\nThe function passed in can log a formatted message to console or remotely as it chooses.\n\n## Creating Partial Middleware\n\nOne of the best things about `compose` is that it itself returns a middleware, so you can create partial middleware chains and reuse code within a project by using `compose` on those partial chains.\nSuppose you have HTTP handlers and non HTTP handlers and the non HTTP handlers don't require authentication/authorization or validation but do require recovery, timing and input mapping.\n\nImplementing multiple middleware stacks looks like:\n\n```javascript\n// middleware.js\nimport { compose } from 'serverless-compose'\n\n// ... OMITTED MIDDLEWARE IMPLEMENTATIONS ...\n\nconst OuterMiddleware = compose(\n  MyRecoveryMiddleware,\n  MyTimingMiddleware,\n)\n\nconst ValidationMiddleware = compose(\n  MyAuthMiddleware,\n  MyEventValidationMiddleware,\n)\n\nexport const HttpMiddleware = compose(\n  OuterMiddleware,\n  ValidationMiddleware,\n  MyEventMappingMiddleware,\n)\n\nexport const RegularMiddleware = compose(\n  OuterMiddleware,\n  MyEventMappingMiddleware,\n)\n```\n\nNow, in the AWS handlers, only the middleware that is needed can be used.\n\n```javascript\nimport { HttpMiddleware, RegularMiddleware } from './middleware'\n\nfunction myHttpHandler(event, context) {\n  // DO SOMETHING...\n}\n\nfunction myRegularHandler(event, context) {\n  // DO SOMETHING ELSE...\n}\n\nexport const handleHttp = HttpMiddleware(myHttpHandler)\nexport const handleCloudwatchEvents = RegularMiddleware(myRegularHandler)\n```\n\n## Building New Middleware\n\nWhile `compose` is a strong function for assembling middleware, the value it provides is as a starting point as a framework for your own unique requirements.\nThe only requirement of a middleware is that it accept a `Handler` as its only argument and return a `Handler`, which means that as long as you follow this rule, you can easily create custom, composable middleware, or assemble middleware out of well known patterns provided by `serverless-compose` via its convenience functions.\n\nBelow is an example for creating your own side effect middleware:\n\n```javascript\n// middleware.js\nimport { mySideEffect } from './my-library'\n\n// we use the convention next to indicate the next handler in the middleware chain\n// since each middleware returns a handler, we pass that inner-defined handler into the outer middleware when we invoke it\nexport function MyMiddleware(next) {\n  return async (event, context) =\u003e {\n    // A lambda with Handler signature\n    await mySideEffect()\n\n    // After calling side effect, actually call the handler\n    return next(event, context)\n  }\n}\n```\n\nAnd that's it.\nNow when assembling your middleware stack, `mySideEffect` will be called before the next middleware and before the handler.\nSide effects or translations of the event or response can be called before or after the next handler.\nUsing the middleware looks like:\n\n```javascript\n// handler.js\nimport { compose, recoveryMiddleware } from 'serverless-compose'\n\nimport { MyMiddleware } from './middleware'\nimport { OtherMadeupMiddleware, SupposeThisExistsMiddleware } from 'shared-lib'\n\nasync function handler(event, context) {\n  // DO SOMETHING ...\n}\n\nexport lambdaHandler = compose(\n  recoveryMiddleware(async (error) =\u003e {console.log(error)})\n  SupposeThisExistsMiddleware,\n  MyMiddleware,\n  OtherMadeupMiddleware,\n)(handler)\n```\n\nNow an error logging recovery middleware will happen outside (before your middleware and after all other middleware have returned).\nThen `SupposeThisExistsMiddleware` will do whatever it does and call your `MyMiddleware` handler before your `MyMiddleware` handler calls `OtherMadeupMiddleware`.\n`OtherMadeupMiddleware`, most likely, will call `handler`.\nIt's wired to do that, but depending on what it does, it may not.\nAs a middleware author, typically, you should call `next` but you don't have to.\nAn example of when you might not call `next` would be an authorization middleware or request validation middleware -- they're there to prevent the handler from being called if it doesn't meet certain criteria.\n\nCheck `src/middleware.ts` for some examples of translating events and responses or optionally not calling next based on input.\nNote that `middleware.ts` contains middleware generators or functions that produce `Middleware`.\nAs an author, you can decide to generate it based on user-defined parameters or hard-code a middleware.\n\nSuppose you want to convert a ping/pong handler into a pong only handler. You could implement a pattern similar to `responseMiddleware` from `middleware.ts` but hardcode the mapping function:\n\n```javascript\nexport function PongOnlyMiddleware(next) {\n  return async (event, context) =\u003e {\n    const response = await next(event, context)\n\n    // Intercept the response if it's \"ping\" and send \"pong\" instead\n    if response === 'ping' {\n      return 'pong'\n    }\n    // Do whatever otherwise\n    return response\n  }\n}\n```\n\nIt's pretty simple to get started making your own middleware.\nIf you're not sure, open an issue in github and ask!\n\nHappy Coding!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDavidJFelix%2Fserverless-compose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDavidJFelix%2Fserverless-compose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDavidJFelix%2Fserverless-compose/lists"}