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

cassava

v2.7.1

Published

AWS API Gateway Router

Downloads

132

Readme

Cassava

AWS API Gateway Router

Find the full documentation at https://giftbit.github.io/cassava/

Routing

There are two ways to add routes to Cassava.

  • route(string|RegExp)
    • simplest method that handles most cases
    • string routes are case insensitive and support path parameters
    • RegExp routes place matching groups in the path parameters
  • route(Route)
    • provides the most flexibility
    • can modify responses before they are sent
    • is the most work to implement

Cassava processes REST events by examining installed routes from top-to-bottom. Cassava works downwards to find the first route that matches and responds, and then works back up to do any post-processing.

A route responds when: it matches the event, has a handle function, and handles the event by returning a response object or Promise that resolves to a response object.

A route can post-process the response when: it matches the event, did not return a response object in handle, and has a postProcess function. Post processing can be used to modify the response or cause some side effect such as logging.

RouteBuilder

RouteBuilder is the simplest way to add a route to Cassava. A RouteBuilder instance is started with router.route(string) or router.route(RegExp), then with chained function calls you can specify the HTTP method, add a handle function or a postProcess function.

The details of handling and post-processing are covered later in this document.

For example...

import * as cassava from "cassava";

const router = new cassava.Router();

// A simple route that only handles GET on /helloWorld
router.route("/helloWorld")
    .method("GET")
    .handler(async evt => {
        return {
            body: "Hello world!"
        };
    });

// A fancier route with a path parameter `name`.
// match egs: `/hello/jeff` or `/Hello/Jeffery`
router.route("/hello/{name}")
    .method("GET")
    .handler(async evt => {
        return {
            body: `Hello ${evt.pathParameters["name"]}!`
        };
    });

// Install the router as the handler for this lambda.
export const handler = router.getLambdaHandler();

Custom Routes

A custom route is one that implements the Route interface: it must have a matches function that accepts a RouterEvent and returns a boolean and at least one of: a handle function or postProcess function.

The details of RouterEvents, handling and post-processing are covered later in this document.

For example...

import * as cassava from "cassava";

const router = new cassava.Router();

// A custom route that comes with Cassava providing console logging of requests.
// This route has both a `handle` and `postProcess` function to log both requests and responses.
router.route(new cassava.routes.LoggingRoute());

// A custom Route that handles PUT or PATCH on any path starting with /upload/
router.route({
    matches: evt => {
        return (evt.httpMethod === "PUT" || evt.httpMethod === "PATCH") &&
            evt.path.startsWith("/upload/");
    },
    handle: async evt => {
        const fileName = evt.path.substring("/upload/".length);
        const fileContents = evt.body;
        // ... store fileContents with fileName
        return {
            statusCode: 204
        };
    }
});

// Install the router as the handler for this lambda.
export const handler = router.getLambdaHandler();

RouterEvents, RouterResponses, handling and postProcessing

RouterEvents are the input to matches and handle functions. They fully describe all information about the REST request including the full body as streaming is not supported.

A handle function takes in a RouterEvent and can return the following: null or undefined to not handle the RouterEvent in which case further routes are consulted; a RouterResponse that represents the response sent to the client; a Promise that resolves to null or undefined which will again let further routes handle the request; a Promise that resolves to a RouterResponse which again will be the response sent to the client.

RouterResponses include the body, an optional HTTP status code (defaults to 200), and optionally any headers that might be set.

A postProcess function takes in both the RouterEvent and the current RouterResponse. It can return null or undefined or a Promise resolving to one of those to not affect the final response; or it can return a RouterResponse or a Promise resolving to a RouterResponse to change the response.

Response serialization

The default assumption is that you're building a JSON-based API so that's the simplest case. By default the response body will be JSON stringified and the header Content-Type set to application/json. This is true even if the body is a string. If you don't want that behavior you have two options:

Manual Content-Type

The first option for returning non-JSON is to set the response body to a string or Buffer, and set the Content-Type header. This works when using a custom route or the route builder. For example:

router.route("/robots")
    .method("GET")
    .handler(async evt => {
        return {
            headers: {
                "Content-Type": "text/csv"
            },
            body: "robot,film\nRobby,Forbidden Planet\nGort,The Day the Earth Stood Still"
        };
    });

This is simple to implement but ignores the client's Accept header. This endpoint will always return csv regardless of what the client asks for.

RouteBuilder.serializers

When using the route builder there is a second option of letting the handler return a complex object as in the JSON case, but defining serializer functions for each response mime type. The appropriate serializer will be chosen based upon the client's Accept header. In the following example the same endpoint can return one of JSON, CSV and XML.

router.route("/robots")
    .method("GET")
    .serializers({
        "application/json": cassava.serializers.json,
        "text/csv": body => new json2csv.Parser({fields: ["robot", "film"]}).parse(body),
        "application/xml": body => jsontoxml({robots: body})
    })
    .handler(async evt => {
        return {
            body: [
              {
                  robot: "Robby",
                  film: "Forbidden Planet"
              },
              {
                  robot: "Gort",
                  film: "The Day the Earth Stood Still"
              }
            ]
        };
    });

In this example CSV serialization is handled by json2csv and XML serialization by jsontoxml. These libraries are not included with Cassava and you're free to choose your own serialization libraries.

RouterEvent Validation

RouterEvent comes with a number of utility functions to validate the event.

  • blacklistQueryStringParameters(...params: string[]) disallow any of the given query parameters
  • requireHeader(field: string) require that a header is set
  • requireHeader(field: string, values: string[], explanation?: string) require that a header is set and takes one of a given list of values
  • requireHeader(field: string, validator: function, explanation?: string) require that a header is set and satisfies the validator function
  • requireQueryStringParameter(param: string) require that a query parameter is set
  • requireQueryStringParameter(param: string, values: string[], explanation?: string) require that a query parameter is set and takes one of a given list of values
  • requireQueryStringParameter(param: string, validator: function, explanation?: string) require that a query parameter is set and satisfies the validator function
  • validateBody(schema: Schema, options?: ValidateBodyOptions) validate the request body using JSON Schema
  • whitelistQueryStringParameters(...params: string[]) disallow any query parameters other than the ones set

An example:

import * as cassava from "cassava";

const router = new cassava.Router();

// Get a location
router.route("/locations/{locationId}")
    .method("GET")
    .handler(async evt => {
        evt.whitelistQueryStringParameters();   // don't allow any query params
        return {
            body: getLocationById(evt.pathParameters.locationId)
        };
    });

// Set a location
router.route("/locations/{locationId}")
    .method("POST")
    .handler(async evt => {
        evt.validateBody({
             type: "object",
             properties: {
                 latitude: { "type": "number" },
                 longitude: { "type": "number" }
             },
             required: ["latitude", "longitude"]
         });
        return {
            body: setLocationId(evt.pathParameters.locationId, evt.body)
        };
    });

// Query for locations
router.route("/locations")
    .method("GET")
    .handler(async evt => {
        evt.requireQueryStringParameter("query");
        return {
            body: getLocationsByQuery(evt.queryStringParameters.query)
        };
    });

// Install the router as the handler for this lambda.
export const handler = router.getLambdaHandler();

The Name

Cassava is a starchy root vegetable grown all over the world. The more you know. ┈┅*