Giter Site home page Giter Site logo

dbartholomae / lambda-middleware Goto Github PK

View Code? Open in Web Editor NEW
151.0 151.0 18.0 1.38 MB

A collection of middleware for AWS lambda functions.

Home Page: https://dbartholomae.github.io/lambda-middleware/

License: MIT License

TypeScript 84.70% JavaScript 10.05% Raku 2.66% Perl 2.58%
aws hacktoberfest lambda lambda-middleware middleware

lambda-middleware's Introduction

Follow on Twitter

Hi there 👋!

I'm Daniel. If you read this, it might well be that I sent you the link so you can learn more about working with me. Or you might just have stumbled upon this page, in which case: Nice to meet you! :)

This is my Personal User Manual. If your experience with me is different or you think there is a way this document might be more useful, feel free to open an issue or suggest a change.

Who am I?

I'm a Berlin based founder, manager and developer. I have a faible for technology, data and self management. Currently my focus is on making it easy to solve business problems with TypeScript based web technology.

My professional history

You can find my professional exploits on my LinkedIn profile.

Where to find me online

It is easiest to reach me via email, but you can also follow me on Twitter. From time to time I also write blog articles.

Understanding me

My believes and how they manifest

  • No one is intrinsically bad. Therefore I always believe in good intentions until proven otherwise.
  • Caring about each other makes life better for everyone. Therefore I try to take care of everyone in my sphere of influence.
  • We should all in principle follow the same rules and therefore I have a hard time with rules that don’t apply universally.
  • Working in a group where everyone gives their best is more pleasant and effective than everyone "just doing their job". This doesn't mean spending every wake hour on the job, or not having off days. It is about attitude. Therefore I try to hold everyone including myself up to the highest standards.
  • Transparency and candor both make working more pleasant and effective. Therefore I like to discuss ideas openly and get feedback from others.
  • Making mistakes and learning from them is an important part of life. Therefore I always look for what to learn from mistakes, never for punishing anyone involved.
  • The only way to find out something is to actually try it. Therefore I am usually looking for an experiment that allows to get feedback with the least energy required. It makes sense to learn from what others have learnt to find the right experiments, but then skipping the experiment all-together is dangerous.
  • Handover of work is the biggest source of waste I’ve seen at companies. Therefore I set up teams that are responsible end-to-end for customer value, and the farther end-to-end this stretches, the better.
  • Especially in software development we tend to plan too much ahead. Therefore I prefer just-in-time optimization: Not too early where I don't know yet what to optimize for, but only when the optimization actually becomes necessary.

What people tell me I'm good at

  • Building and coaching teams and individuals to achieve great things while having fun.
  • Meaning well and deeply caring about the people I work with.
  • Helping others to be successful.
  • Rapidly understanding complex problems by probing with questions and thinking through topics.
  • Forcing decisions not to do things instead of wasting time on something that might not be the most effective way to go forward.

My quirks

  • I can be very direct. Please let me know if I am too direct. Feel free to be direct and open with me yourself to let me know.
  • It can be hard to read my emotions. Sometimes this means that I don’t seem appropriately concerned, but me remaining calm does not mean that I do not care or do not take the situation seriously. If you are unsure about my emotions, feel free to just ask.
  • I'm a big fan of being organised digitally. I might sometimes put too much focus on having a clean todo list, an empty inbox ("inbox zero"), or similar. If in doubt, just ask me if something really needs to be cleaned up, and I will reflect on whether there is actual merit to it or it's just my personal preference.
  • Entertaining large groups of people isn't my strong suit. I prefer to meet one-on-one and in smaller groups.
  • I'm especially bad at remembering things. This is one of the reasons why I prefer written communication where I can just look things up.
  • Following orders can be hard for me. Specifically it is hard for me to do things without understanding the reasoning behind it, or at least why there is no time to understand that reasoning right now. This also sometimes leads me to interfere with other people's responsibilities.

Handling stress

I am not easily stressed and will remain calm even in situations where others might freak out. Should I ever get stressed out, the symptoms are that I withdraw and become even more silent. If you feel I no longer openly and transparently communicate, please privately tell me so I can reflect and bring back communication.

Working with me

Productive times

My productive times can vary by day. I tend to be more productive later in the day, though, and usually prefer not starting work before 09:00 am.

Communicating with me

Feel free to be direct with me. I am not easily offended.

For communication I usually prefer written communication above talking as it gives you the time to optimize your message (e. g. using a top-down structure) and me the ability to understand in my own time and potentially include others into the conversation where beneficial.

I feel confident in my triaging skills. If in doubt, you can include me in written communication and I will take care of sorting out what is important for me and what is not.

If you would prefer personal communication, feel free to schedule something into my calendar. My calendar is usually well-managed.

Emergency communication

If you need an immediate response from me, please send me an SMS or call me on the phone. Due to the nature of my work I'm trying to manage my time in larger, uninterrupted chunks where possible and might not check e. g. email or Slack for longer amounts of time.

If you don't have my phone number but think you might need it, just let me know.

Other

Preferred pronouns

My preferred pronouns are they/them, but I'm also comfortable with he/him/his.

How to pronounce my name

Daniel can either be pronounced Daniel in German or Daniel in English. Bartholomae is pronounced as ˈbatolome. I don't really care though as long as I know that you are talking about me.

Dietary restrictions

  • I don't eat cheese, don't drink alcohol and don't drink coffee. Weird combination, huh? :)
  • I try to avoid meat, or at least to buy organic, but I'm not very good at following through with this yet.
  • If in doubt, Thai or Vietnamese cuisine is always fine.

My favorite business books

You can find my professional book recommendations on Goodreads.

Obligatory fun fact

I once managed to forget my own first name for a couple seconds when asked to introduce myself.

lambda-middleware's People

Contributors

actions-user avatar buresmi7 avatar dbartholomae avatar gcichosz avatar greenkeeper[bot] avatar matt-jenner avatar mergify[bot] avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lambda-middleware's Issues

Help with composeHandler response

I'm having a hard time determining the return type from a composeHandler. Likely due to a lack TypeScript knowleged.

Everything seems great when using out of the box handlers:

import { ProxyHandler } from "aws-lambda";
import { PromiseHandler } from "@lambda-middleware/utils";
import { composeHandler } from "@lambda-middleware/compose";
import { cors } from "@lambda-middleware/cors";
import { errorHandler } from "@lambda-middleware/http-error-handler";

const helloWorld: PromiseHandler = async () => {
  return {
    body: "Hello World",
    statusCode: 200,
  };
};

export const handler: ProxyHandler = composeHandler(
  errorHandler(),
  cors(),
  helloWorld
);

But with middleware which alter the event, something isn't right:

import {
  ProxyHandler,
  Context,
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
} from "aws-lambda";
import { PromiseHandler } from "@lambda-middleware/utils";
import { composeHandler } from "@lambda-middleware/compose";

const fooMiddleware = () => <E extends APIGatewayProxyEvent>(
  handler: PromiseHandler<E & { foo: string }, APIGatewayProxyResult>
): PromiseHandler<E, APIGatewayProxyResult> => async (
  event: E,
  context: Context
) => {
  return handler({ ...event, foo: "bar" }, context);
};

const barMiddleware = () => <E extends APIGatewayProxyEvent>(
  handler: PromiseHandler<E & { bar: string }, APIGatewayProxyResult>
): PromiseHandler<E, APIGatewayProxyResult> => async (
  event: E,
  context: Context
) => {
  return handler({ ...event, bar: "foo" }, context);
};

const helloWorldEvent = async (
  event: APIGatewayProxyEvent & { foo: string; bar: string }
) => {
  console.log("Foo", event.foo);
  console.log("Bar", event.bar);

  return {
    body: "Hello World",
    statusCode: 200,
  };
};

// Not happy w/ ProxyHandler
export const handler: ProxyHandler = composeHandler(
  fooMiddleware(),
  barMiddleware(),
  helloWorldEvent
);

Can you please show how to add cors() to a compose chain?

Is your feature request related to a problem? Please describe.
I'm trying to add the cors middleware to a compose chain, and can't seem to find the right combination. Here's my current attempt:

const corsConfig: CorsMiddlewareOptions = {
  allowedHeaders: ['*'],
  cacheControl: 'max-age: 300',
  allowCredentials: true,
  exposedHeaders: ['X-Custom-Header'],
  maxAge: 300,
  allowedMethods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
  optionsSuccessStatus: 204,
  allowedOrigins: ['https://mysite.com'],
  preflightContinue: false,
}

export const standardHttp = compose<APIGatewayEvent, APIGatewayProxyResult>(
  createJWTForLocal,
  typeormInitializer,
  doNotWait(),
  jsonSerializer(),
  jsonDeserializer(),
  timingLogMiddleware(logDuration),
  errorHandler,
  cors(corsConfig)
)

** Would you be willing to help with a PR? **

  • Yes, absolutely
  • Yes, with some guidance
  • Unfortunately no time :'-(

Describe the solution you'd like

Ideally, I'd like to add cors to this chain to add to all lambda functions

Describe alternatives you've considered

None

Additional context

Add any other context or screenshots about the feature request here.

Unable to JSON serialize type with optional keys

Describe the bug

The json serializer works fine for object literals, however if passed a type with optional members (I generally return models from my handlers, which usually have optional fields), it doesn't typecheck.

To Reproduce

  describe("with a handler returning a type with optional members", () => {
    let response: any;

    type Thing = {
        id?: string
        foo: string
    }

    beforeEach(async () => {
      const handlerResponse: Thing = { foo: "bar" };
      const handler = async () => handlerResponse;
      const handlerWithMiddleware = jsonSerializer()(handler);
      response = await handlerWithMiddleware(createEvent({}), createContext());
    });

    it("returns 200", async () => {
      expect(response).toMatchObject({ statusCode: 200});
    });

    it("returns serialized non-optional members", async () => {
      expect(response.body).toEqual("{\"foo\":\"bar\"}");
    });
  })

yields

    src/JsonSerializer.test.ts:38:54 - error TS2345: Argument of type '() => Promise<Thing>' is not assignable to parameter of type 'PromiseHandler<APIGatewayProxyEvent, { [key: string]: JSONPrimitive; } | JSONObject[] | undefined>'.
      Type 'Promise<Thing>' is not assignable to type 'Promise<{ [key: string]: JSONPrimitive; } | JSONObject[] | undefined>'.
        Type 'Thing' is not assignable to type '{ [key: string]: JSONPrimitive; } | JSONObject[] | undefined'.
          Type 'Thing' is not assignable to type '{ [key: string]: JSONPrimitive; }'.
            Property 'id' is incompatible with index signature.
              Type 'string | undefined' is not assignable to type 'JSONPrimitive'.
                Type 'undefined' is not assignable to type 'JSONPrimitive'.

Expected behavior

Type check passes as the optional key is still of the right type.

Additional context

This appears to be a bit of a typescript wart, as optional members are treated as equivalent to having | undefined under strict null checks, which doesn't really make sense to me given that even in underlying JS hasOwnProperty can distinguish the two cases.

This seems like it can be fixed by redefining type JSONPrimitive = string | number | boolean | JSONObject | undefined;, though this isn't technically valid JSON, JSON.stringify has a relatively defined behavior for it.

I think there is a way to do this cleanly with keyof/Pick, trying to work it out...

EDIT: I should also note that this doesn't work at all if the returned value is typed as interface; that seems to really break the index signature inference

Interest in a path parser middleware?

Is your feature request related to a problem? Please describe.
Certain lambda function hosts (netlify functions in particular) do not provide a path parser/do not populate pathParameters, nor offer a way to pack path params into query params using rewrites. I wrote a very simple path parser middleware that addresses this.

Would you be willing to help with a PR?

  • Yes, absolutely
  • Yes, with some guidance
  • Unfortunately no time :'-(

Describe the solution you'd like

The code is very simple, most of it is here:
https://gist.github.com/parkan/f0b95f343e18aaadd1bb93b4ca67806d

I'm using this in production (ish), only thing I see being needed is e2e tests.

Describe alternatives you've considered

The netlify dev (local testing) env provides packing of path params into query params, however this doesn't work in production. The only other alternative is manually unpacking them in the handler.

Additional context

The downside here is that the handler wrapper needs to know the path expression to parse out the params, but I don't see a way to work around it, aside from parsing _redirects at build time.

@lambda-middleware/utils package is pulling in the entire aws-sdk package

Describe the bug

The @lambda-middleware/utils is indirectly including the entire aws-sdk package via the aws-lambda package which is increasing my bundle size to ~350kb

To Reproduce

❯ yarn init -y
❯ yarn add @lambda-middleware/utils
❯ yarn why aws-sdk
yarn why v1.22.10
[1/4] �🤔  Why do we have the module "aws-sdk"...?
[2/4] �🚚  Initialising dependency graph...
[3/4] �🔍  Finding dependency...
[4/4] �🚡  Calculating file sizes...
=> Found "[email protected]"
info Reasons this module exists
   - "@lambda-middleware#utils#aws-lambda" depends on it
   - Hoisted from "@lambda-middleware#utils#aws-lambda#aws-sdk"
info Disk size without dependencies: "59.79MB"
info Disk size with unique dependencies: "61.05MB"
info Disk size with transitive dependencies: "61.3MB"
info Number of shared dependencies: 9

Expected behavior

The aws-sdk not be included transitively

Additional context

I believe the core issue is that this package imports both @types/aws-lambda and aws-lambda even though only the type information is needed.

You should be able to just remove the aws-lambda import

Should doNotWait middleware make context.callbackWaitsForEmptyEventLoop true instead of boolean?

It's not a really bug, but a type issue, and even that I'm not really sure.

Describe the bug

A clear and concise description of what the bug is.
When doNotWait middleware is used in composeHandler, the type for context.callbackWaitsForEmptyEventLoop should be true, not boolean.

To Reproduce

The following minimal code reproduces the error:

import { composeHandler } from "@lambda-middleware/compose"
import { doNotWait } from "@lambda-middleware/do-not-wait"

export const handler = composeHandler(
      doNotWait(),
      async (evt, ctx) => {
            ctx.callbackWaitsForEmptyEventLoop // It is typed as boolean, but should be true
            return {
                  body: 'OK',
                  statusCode: 200
            }
      }
)

Expected behavior

A clear and concise description of what you expected to happen.
It to be typed true.

Additional context

Add any other context about the problem here.

Question - Composing multiple middleware

I'm trying to expose a wrapper function so that all my lambda handlers have the same middleware applied to them.

const wrapper = (handler) =>
  compose(
    // add cors headers last so even error responses from the
    // errorHandler middleware have cors headers applied
    cors(),
    errorHandler(),
    jsonSerializer(),
    classValidator(),
  )(handler)

However I'm struggling to get the type information to line up.

Is it possible to chain these middlewares like this as you can do using middy?

jsonSerializer does not pass through correct event type

Describe the bug

Hello it's me again 🙂

jsonSerializer (and I think some of the other middlewares as well) don't respect the pattern of generically passing the event from the previous middleware described in the README so the type information about the modified event is lost, replaced with base APIGatewayEvent

To Reproduce

compose(
  errorHandler(),
  jwtAuth(...),
  jsonSerializer()
)(handler) // the handler gets APIGatewayEvent without AuthorizedEvent

Expected behavior

Type information is preserved in the chain

Additional context

This should be a simple fix, replace

export const jsonSerializer = () => (
  handler: PromiseHandler<APIGatewayEvent, JSONObject | undefined>
) => 
...

with

export const jsonSerializer = () => (
  <E extends APIGatewayProxyEvent>(handler: PromiseHandler<APIGatewayEvent, JSONObject | undefined>): PromiseHandler<E, APIGatewayProxyResult> =>
...

happy to PR this!

Add typeorm middleware for connecting to a database

Is your feature request related to a problem? Please describe.
Many of our lambdas need to connect to a relational database with help of typeorm. This logic could be abstacted out similar to middy's db-manager. This should also allow to wire the database into a dependency injection framework.

Describe the solution you'd like
A minimal version should work like this, reading the setup needed from the typeorm-typical env vars.

export const handler = connectDatabase({
  container: Container
})
( ..)

Potentially should allow to get credentials via AWS.

Describe alternatives you've considered
Potentially this should be more abstract, allowing other kinds of ORMs like knex. Currently I think a simple middleware focussing only on typeorm makes more sense, with individual middlewares for individual ORMs.

Middleware ordering causes type conflicts on remaining middleware

I tried to follow the bodyParser example and build my own middleware which alters the event. I was also adding the first party cors middleware to the routine which produced some type errors I cannot figure out.

In the first scenario, the first middleware is adding 'foo' to the event -> cors -> business logic which doesn't hold the type reference:

import {
  Context,
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
} from "aws-lambda";
import { PromiseHandler } from "@lambda-middleware/utils";
import { composeHandler } from "@lambda-middleware/compose";
import { cors } from "@lambda-middleware/cors";

const fooMiddleware = () => <E extends APIGatewayProxyEvent>(
  handler: PromiseHandler<E & { foo: string }, APIGatewayProxyResult>
): PromiseHandler<E, APIGatewayProxyResult> => async (
  event: E,
  context: Context
) => {
  return handler({ ...event, foo: "bar" }, context);
};

const helloWorldEvent = async (
  event: APIGatewayProxyEvent & { foo: string }
) => {
  console.log("Foo", event.foo);

  return {
    body: "Hello World",
    statusCode: 200,
  };
};

export const handlerComposed = composeHandler(
  fooMiddleware(),
  cors(),
  helloWorldEvent  // <- not happy
);

Anything stick out?

Add io-ts validator middleware

Is your feature request related to a problem? Please describe.
I spend a lot of time writing and rewriting deserialization and type validation code in my lambdas. I've just started a project where we have a large number of endpoints (roughly 40 of them) and I really didn't fancy rewriting this code again and again.

As a result, I've written two middlewares compatible with your lambda-middleware library and I'd like to see if you'd like them as part of the main package before I publish them as separate packages, this feature request is for the io-ts validator.

** Would you be willing to help with a PR? **

  • Yes, absolutely (already written and working, separating from existing project at-present)
  • Yes, with some guidance
  • Unfortunately no time :'-(

Describe the solution you'd like

The io-ts validator is loosely based on ideas from the class validator middleware, I decided to build it as I wanted to type validate a payload without having to add it a class and I didn't want to have to enable experimental typescript features.

It takes an io-ts codec and a type alias (to report errors in a human-readable fashion) and returns the middleware that will validate that specific type of object.

It replaces the body of the incoming event for the wrapped handler with the typed object (much like the JSON Deserializer middleware).

Describe alternatives you've considered

  • Writing the above functionality within the handler.
  • Using class validator - I don't want to enable experimental functionality, and it restricts me to using classes
  • Writing a compatible middleware and keeping it within my code.
  • Releasing this as a separate project and package (but it seemed to make more sense and provide more visibility to add it to the main project though, and a nice way to give back to a really useful project).

Additional implementation notes

Why io-ts? (or rather.... why not Zod or Class Validator?)

Class validator requires classes, and I've already written validators in io-ts. Why io-ts vs zod? I have experience with both, but zod explicitly does NOT map the validated object meaning that you have to write a separate mapper and it clutters things up a bit more than you might otherwise need. Passing io-ts codecs in seemed like a nice clean solution (despite all the annoying functional programming shenanigans in using io-ts).

Dependencies on other Middlewares (ordering)

Currently, in my app's implementation of this middleware, it expects the body to already be an object (as I control the middleware order and I didn't need to deserialize as part of this middleware) - if this is ok, I can make this an expectation of using the middleware, or I could add deserialization to make it a bit more generic but it would mean we have some repeated functionality with the JSON Deserializer middleware.

Changing Properties vs Adding New Properties to the APIGatewayProxyEvent

Currently I'm stripping the body and replacing it with the typed object, this does mean it could have negative effects on a downstream middleware, but I think it is cleaner for my code and reflects the object being transformed into something my handler will want to work with.

If you don't want that, I could add another distinct property in addition to the original body but seeing as you used a similar method in the class-validator middleware, I hope you don't have any issues with this? :)

Error Handling

I like the idea of "typed" errors, as such my middleware will probably throw a distinct ValidationError which will contain validation information that can be returned to the user in a BadRequest output.

I extend Error and add any relevant properties to the extended error class (so if it's logged out I would get useful semantic logging).

You can then use these with http-error-handler middleware (switching on error name) or you could use an app-specific error handler middleware at the top level (as I'm doing in my code at-present).

class-validator seems to break the type inference for the handler

Describe the bug

When classValidator is used in composeHandler, the types for event, and context becomes any.
The return type is lost as well.

To Reproduce

The following minimal code reproduces the error:

import "reflect-metadata"
import { composeHandler } from "@lambda-middleware/compose"
import { jsonSerializer } from "@lambda-middleware/json-serializer"
import { errorHandler } from "@lambda-middleware/http-error-handler"
import { classValidator } from '@lambda-middleware/class-validator'
import { jsonDeserializer } from "@lambda-middleware/json-deserializer"
import { doNotWait } from "@lambda-middleware/do-not-wait"
import { IsString } from "class-validator"


class NameBody {
      constructor(firstName: string, lastName: string) {
            this.firstName = firstName;
            this.lastName = lastName;
      }

      @IsString()
      public firstName: string;

      @IsString()
      public lastName: string;
}


export const handler = composeHandler(
      errorHandler(),
      doNotWait(),
      jsonDeserializer(),
      jsonSerializer(),
      classValidator({
            bodyType: NameBody,
      }),
      async (evt, ctx) => {
            return {

            }
      }
)

Expected behavior

A clear and concise description of what you expected to happen.
Infer event, context, and return type.

Additional context

Here is a screenshot from the code snippet, it weirdly expects a then:
image

JSONSchema validator with AJV

Is your feature request related to a problem? Please describe.

I've been using AJV to validate my request payloads. I could see this being easily adapted to an official lambda-middleware.

JSONSchema is a well documented spec. AJV can return very detailed errors. It also doesn't require adapting your code to use classes.

** Would you be willing to help with a PR? **

  • Yes, absolutely
  • Yes, with some guidance
  • Unfortunately no time :'-(

Describe the solution you'd like

Turn the following into middleware.

// JSONSchema format
const bodySchema = {
  type: 'object',
  properties: {
    body: { type: 'string' },
    header: { type: 'string' },
    footer: { type: 'string' },
    encoding: { type: 'string' },
    options: { type: 'object', additionalProperties: true },
  },
  required: ['body'],
  additionalProperties: false,
}
const ajv = new Ajv()
const validate = ajv.compile(bodySchema)

const payload = JSON.parse(event.body)
const valid = validate(payload)
if (!valid) {
  console.log(validate.errors)
}

Describe alternatives you've considered

What I'm doing now is already the alternative.

Add middleware to serialize json data into an http response

It should take as input an object and then serialize it into an AWS lambda response object with body, statusCode (200, or 204 if the input is undefined) and set the right headers (content-type). It should not spring into action if the event headers ask for a different content-type.

Add a JSON Deserializer Middleware

Is your feature request related to a problem? Please describe.
I spend a lot of time writing and rewriting deserialization and type validation code in my lambdas. I've just started a project where we have a large number of endpoints (roughly 40 of them) and I really didn't fancy rewriting this code again and again.

As a result, I've written two middlewares compatible with your lambda-middleware library and I'd like to see if you'd like them as part of the main package before I publish them as separate packages, this feature request is for the JSON deserializer.

Would you be willing to help with a PR?

  • Yes, absolutely (already written, writing tests now)
  • Yes, with some guidance
  • Unfortunately no time :'-(

Describe the solution you'd like

This takes incoming event with a JSON payload, deserializes it to an object and replaces the body property with it's object equivalent and throws a custom error if JSON serialization fails (so it can be picked up by a global error handler middleware).

The implementation types would be something like this:

<E extends APIGatewayProxyEvent, R>() => PromiseHandler<Omit<E, "body"> & { body: {} }, R> => R

It does the following:

  • Asserts that the mime type on the request is JSON - if not it should throw a BadRequestError (if we're setting up a middleware to deserialize json, a non-json request should be considered "bad" in my opinion?).
  • Attempts to deserialize the payload - if it fails it should throw a RequestBodyNotJsonError.
  • Switches the body property on the request with the object and passes it to the handler.

Describe alternatives you've considered

  • Writing the above functionality within the handler.
  • Writing a compatible middleware and keeping it within my code.
  • Releasing this as a separate project and package (but it seemed to make more sense and provide more visibility to add it to the main project though, and a nice way to give back to a really useful project).

Additional implementation notes/considerations

Changing Properties vs Adding New Properties to the APIGatewayProxyEvent

Currently I'm stripping the body and replacing it with an object, this does mean it could have negative effects on a downstream middleware, but I think it is cleaner for my code and reflects the object being transformed into something my handler will want to work with.

If you don't want that, I could add another bodyObject property in addition to the original body but seeing as you used a similar method in the class-validator middleware, I hope you don't have any issues with this? :)

Error Handling

I like the idea of "typed" errors, as such my middleware will probably use two examples of this (and my implementation uses these).

I extend Error and add any relevant properties to the extended error class (so if it's logged out I would get useful semantic logging).

You can then use these with http-error-handler middleware (switching on error name) or I could use an app-specific error handler middleware at the top level (as I'm doing in my code at-present).

Enable Github Actions to automatically build

Building should include:

  • audit dependencies
  • install dependencies via rush
  • run linting
  • run unit tests
  • report test coverage to codecov
  • run integration tests
  • publish via rush and/or semantic release both to Github and npm

Allowing PromiseHandler to have generic Context

I might be missing something here, but what do you think about adding Context as a generic to the ProxyHandler. I seem to hit a wall when altering it and passing those alterations to the next middleware.

I followed the instructions to creating middleware, but didn't see any references on how to alter the context. As a result, I explored with adding a context generic to the PromiseHandler

New PromiseHandler

export type ContextPromiseHandler<
  TEvent = any,
  TResult = any,
  TContext = Context
> = (event: TEvent, context: TContext) => Promise<TResult>;

And an example of altering the context

// Add `foo` to context
const fooContextMiddleware = () => <
  E extends APIGatewayProxyEvent,
  C extends Context
>(
  handler: ContextPromiseHandler<E, APIGatewayProxyResult, C & { foo: string }>
): ContextPromiseHandler<E, APIGatewayProxyResult, C> => async (
  event: E,
  context: C
) => {
  return handler(event, { ...context, foo: "bar" });
};

// Add `bar` to context
const barContextMiddleware = () => <
  E extends APIGatewayProxyEvent,
  C extends Context
>(
  handler: ContextPromiseHandler<E, APIGatewayProxyResult, C & { bar: string }>
): ContextPromiseHandler<E, APIGatewayProxyResult, C> => async (
  event: E,
  context: C
) => {
  return handler(event, { ...context, bar: "foo" });
};

// Business
const helloWorldContext = async (
  event: APIGatewayProxyEvent,
  context: Context & { foo: string; bar: string }
) => {
  console.log("Foo", context.foo);
  console.log("Bar", context.bar);

  return {
    body: "Hello World",
    statusCode: 200,
  };
};

// In strict mode so using
export const handler = composeHandler(
  fooMiddleware(),
  barMiddleware(),
  helloWorldContext
);

Allow deserializeBody to work with APIGatewayProxyEventV2

Is your feature request related to a problem? Please describe.
deserializeBody() currently assumes an event type APIGatewayProxyEvent. It would be helpful if it could also support APIGatewayProxyEventV2

** Would you be willing to help with a PR? **

  • Yes, absolutely
  • Yes, with some guidance
  • Unfortunately no time :'-(

Describe the solution you'd like

Allow deserializeBody() to be used with handlers using events of type APIGatewayProxyEventV2

Describe alternatives you've considered

I would be happy to extend this PR to include all APIGateway Proxy Event types (e.g., APIGatewayProxyEventV2WithJWTAuthorizer) as well (perhaps by creating a union type that's reused throughout the library), or to create a follow-on PR that accomplishes this

Compatibility with Http API

At the moment, cors middleware seems to not support a version 2.0 payload of the HTTP API for AWS Lambda. There, the HTTP method is available under event.requestContext.http.method not event.httpMethod.

Here's the existing code:

    if (event.httpMethod.toLowerCase() === "options") {
      return handlePreflightRequest(runHandler, event, fullOptions);
    }

** Would you be willing to help with a PR? **

  • Yes, absolutely
  • Yes, with some guidance
  • Unfortunately no time :'-(

Describe the solution you'd like

The handler should be able to dynamically understand an HTTP API payload.

Describe alternatives you've considered

I have removed the dedicated middleware config for the APIs in question and have instead set cors to true at the API Gateway level. This works in my case but will not allow fine grained control that might be needed for sophisticated cases.

Additional context

None

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.