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 🙏

© 2024 – Pkg Stats / Ryan Hefner

simple-lambda-handlers

v0.9.2

Published

a simple and opinionated lambda handler library, built with middy

Downloads

264

Readme

simple-lambda-handlers

a simple and opinionated lambda handler library, built with middy.

installation

npm install --save simple-lambda-handlers

usage

Standard Handler

Here is a quick example that shows how you can wrap your handler logic, do additional validation beyond the input validation schema, and return a response.

// e.g., in `src/handlers/sendUserNotification.ts
import { BadRequestError } from '@ehmpathy/error-fns';
import { createStandardHandler } from 'simple-lambda-handlers';
import Joi from 'joi';

const schema = Joi.object().keys({
  userUuid: Joi.string().uuid().required(),
  message: Joi.string().uuid().required(),
});

const handle = async ({
  event,
}: {
  event: {
    userUuid: string;
    message: string;
  };
}) => {
  // any additional validation you may want
  if (message.includes(PROFANITY)) throw new BadRequestError('message should not include profanity'); // wont show up as cloudwatch error, but will return user an error, since `instanceof BadRequestError`

  // do your business logic here (e.g., call your logic layer)
  const awesomeResponse = await sendUserNotification();

  // return something
  return { awesomeResponse };
};

export const handler = createStandardHandler({
  log,
  schema,
  logic: handle,
});

Api Gateway Handler

Interacting with an APIGateway involves lots of additional concerns beyond those of a standard handler. This library exposes createApiGatewayHandler to quickly considerations such as statusCodes, cors, body serialization, and best security practices for headers.

Here is an example that supports CORS with credentials as well as an auth token passed in the header.

// e.g., in `src/handlers/sendUserNotification.ts
import { createApiGatewayHandler, ApiGatewayHandlerLogic, HTTPStatusCode } from 'simple-lambda-handlers';
import Joi from 'joi';

const schema = Joi.object()
  .keys({
    httpMethod: Joi.string().valid('POST').required(), // if you only allow certain http methods, you can enforce that here
    headers: Joi.object()
      .keys({
        authorization: Joi.string().required(), // if your api requires an auth token, you can enforce that its sent here
      })
      .required()
      .unknown(),
    body: Joi.object()
      .keys({
        userUuid: Joi.string().uuid().required(),
        message: Joi.string().uuid().required(),
      })
      .required(),
  })
  .unknown(true); // api gateway object will have more keys - we just care about the ones above in this example

const handle: ApiGatewayHandlerLogic = async ({
  headers,
  body,
}: {
  headers: { authorization: string };
  body: { userId: string; message: string };
}): Promise<{ statusCode: HTTPStatusCode; body: { awesomeResponse } }> => {
  // any additional validation you may want
  if (message.includes(PROFANITY)) throw new BadRequestError('message should not include profanity'); // will result in a `{ statusCode: 400, body: { errorMessage: 'message should not include profanity' } }` response and wont showup in cloudwatch as an error, since `instanceof BadRequestError`

  // do your business logic here (e.g., call your logic layer)
  const awesomeResponse = await sendUserNotification();

  // if your code had an error somewhere along the code path
  if (codeHadError) throw new Error('just to show what would happen if there is an internal service error'); // will result in a `{ statusCode: 500 }` response, without any details of the error, to ensure no secrets are leaked in unexpected error stacks

  // return something
  return { statusCode: HTTPStatusCode.SUCCESS_200, body: { awesomeResponse } };
};

export const handler = createApiGatewayHandler({
  log,
  schema,
  logic: handle,
  cors: {
    origins: ['yoursite.com', 'yoursite.dev'], // e.g., allow requests only from these two domains
    withCredentials: true, // e.g., with credentials, so that the browser will send cookies
  },
});

features

Input Output Logging

Whats the first thing you want to know when something goes wrong? "What went wrong?". Whats next? "Why?". And after that? "How can I reproduce it?".

By logging the inputs, outputs, and errors - we answer these questions. Logging the inputs lets us reproduce the problem, logging the outputs lets us debug downstream responses, and logging errors tells us what and why things went wrong.

Specifically:

// on input
log.debug('handler.input', { event: handler.event });

// on output
log.debug('handler.output', { response: handler.response });

// on error
log.error('handler.output', { errorMessage: handler.error.message, stackTrace: handler.error.stack });

Bad Request Error

Sometimes users make requests that are invalid - or don't make logical sense. These should not be reported in cloudwatch as errors with your lambda, but should still return an error to your users.

This library takes care of that, so that the only errors that are reported as invocation errors in cloudwatch are those that were unexpected or really were internal service errors.

Tip: use a lambda client like simple-lambda-client to hydrate the error for the caller if invoking a lambda directly

Example:

import { BadRequestError } from '@ehmpathy/error-fns';

// ...
const user = await findUserByUuid({ uuid });
if (!user) throw new BadRequestError(`user not found for uuid '${uuid}'`);

In this example, the user would receive an error response in their payload - but we would not report this as an error with your lambda to cloudwatch.

Why? Because in this case, nothing is going wrong with your service - the user simply gave us a uuid that doesn't relate to a real user, and we want to tell the user thats invalid.

Input Validation

Run time type validation is not given by default in typescript (or javascript) - but can be added explicitly. This is one of the best things you can do to help developers debug and "fail fast".

By validating the inputs that your users pass in, you can make sure that your services wont throw ambiguous errors and do strange things - just because of some unexpected input. Additionally, by constraining your input thoroughly like this, you make it easy for users of your services to debug what is wrong with their request.

This libraries validation error returns a very explicit definition of exactly why their inputs failed validation, making debugging easy.

Example:

const schema = Joi.object().keys({
  userUuid: Joi.string().uuid().required(),
  message: Joi.string().uuid().required(),
});

interface SendUserNotificationEvent {
  userUuid: string;
  message: string;
}
const handle = ({ event }: { event: SendUserNotificationEvent }) => {
  // do things here...
};

export const handler = createStandardHandler({
  log,
  schema,
  logic: handle,
});

In this example, if the user passes in a uuid instead of a userUuid, they would be told that they are missing a required input, userUuid, with a BadRequestError.

Extensibility

This library is built upon middy - and can be extended like any middy chain. For example:

export const handler = createStandardHandler({
  log,
  schema,
  logic: handle,
})
  .use(middleware1)
  .use(middleware2);