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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ajentify/routed-api

v0.4.1

Published

Reusable RoutedApi CDK construct: routes.json -> Lambdas + HTTP API + CORS + (optional) Route53 domain. Supports per-lambda and single-lambda deployment modes.

Readme

@ajentify/routed-api

A reusable CDK construct that takes a routes.json + a folder of TypeScript handler files and gives you:

  • Lambdas per route or a single-lambda router (configurable via deploymentMode)
  • DynamoDB permissions wired up declaratively
  • An HTTP API (API Gateway v2) with CORS
  • An optional Route53 + ACM custom domain
  • Everything else in your CDK stack stays exactly as you write it

No build step, no codegen, no separate deploy tool. Drop it into any CDK stack.

Why HTTP API v2?

HTTP APIs are ~70% cheaper than REST APIs, have native CORS, and are perfectly adequate for typical app backends. The construct hides API-Gateway-isms behind a single routes.json.

Install

npm install @ajentify/routed-api

Peer dependencies (your CDK app must already have these):

npm install aws-cdk-lib constructs

Minimum usage

import * as cdk from 'aws-cdk-lib';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as path from 'node:path';
import { RoutedApi } from '@ajentify/routed-api';

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const todosTable = new dynamodb.Table(this, 'TodosTable', {
      partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    const api = new RoutedApi(this, 'TodoApi', {
      routesFile: path.join(__dirname, 'routes.json'),
      lambdaSrcDir: path.join(__dirname, 'lambda'),
      tables: { todos: todosTable },
      cors: { origins: ['http://localhost:3000'] },
    });

    new cdk.CfnOutput(this, 'ApiUrl', { value: api.url });
  }
}

routes.json

{
  "defaultMemoryMb": 256,
  "defaultTimeoutSeconds": 15,
  "defaultEnvironment": { "LOG_LEVEL": "info" },
  "defaultTableAccess": { "todos": "readWrite" },

  "routes": [
    {
      "path": "/todos",
      "method": "GET",
      "handler": "todos/list.ts",
      "tables": { "todos": "read" }
    },
    {
      "path": "/todos",
      "method": "POST",
      "handler": "todos/create.ts"
    },
    {
      "path": "/todos/{id}",
      "method": "DELETE",
      "handler": "todos/delete.ts"
    }
  ]
}

Route fields

| Field | Type | Default | | ---------------- | ---------------------------------------------------------------- | ---------------------------------------- | | path | string (e.g. "/todos", "/todos/{id}") | required | | method | "GET" \| "POST" \| "PUT" \| "PATCH" \| "DELETE" \| ... \| "ANY" | required | | handler | path to .ts file, relative to lambdaSrcDir | required | | export | exported function name in that file | "handler" | | memoryMb | number | defaultMemoryMb | | timeoutSeconds | number | defaultTimeoutSeconds | | environment | Record<string,string> | {} | | tables | string[] or Record<string, "read"\|"write"\|"readWrite"> | inherits defaultTableAccess from props |

Default table access

Pass one of:

  • "allReadWrite" — every route can read & write every table you pass in (default)
  • "allRead" — every route gets read-only on every table
  • "none" — no table access unless the route asks
  • Record<string, "read"|"write"|"readWrite"> — explicit per-table defaults

For every grant, the lambda also receives ${NAME}_TABLE_NAME as an env var (e.g. TODOS_TABLE_NAME).

Custom IAM policies

Use the policies prop to attach additional IAM permissions beyond DynamoDB table access — for example SES, S3, SQS, or any other AWS service:

import * as iam from 'aws-cdk-lib/aws-iam';

new RoutedApi(this, 'MyApi', {
  // ...
  policies: [
    new iam.PolicyStatement({
      actions: ['ses:SendEmail', 'ses:SendRawEmail'],
      resources: ['*'],
    }),
    new iam.PolicyStatement({
      actions: ['s3:GetObject'],
      resources: ['arn:aws:s3:::my-bucket/*'],
    }),
  ],
});

In single-lambda mode, policies are attached to the single router function. In lambda-per-route mode, policies are attached to every function.

Environment variables from a .env file

Instead of passing a large defaultEnvironment object inline, point to a .env file with the envFile prop:

new RoutedApi(this, 'MyApi', {
  envFile: path.join(__dirname, '.env'),
  // ...
});

The file uses standard KEY=VALUE syntax with build-time variable substitution:

# Static values
COOKIE_NAME=my_app_refresh

# Substitute from CDK build-time process.env, with fallback defaults
JWT_SECRET=${JWT_SECRET:-dev-secret-change-in-production}
API_BASE_URL=${API_BASE_URL:-http://localhost:3000}

# No fallback — empty string if unset
GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}

Variables from the env file are merged with (and overridden by) defaultEnvironment and per-route environment settings.

CORS

cors: {
  origins: ['http://localhost:3000', 'https://app.example.com'],
  // methods, headers, allowCredentials, maxAgeSeconds are all optional
}
// or pass false to disable preflight entirely

Sensible defaults are applied — Content-Type, Authorization, X-Api-Key, X-Amz-Date, X-Amz-Security-Token, X-Requested-With.

Custom domain (Route53 + ACM)

domain: {
  recordName: 'api.example.com',
  hostedZoneName: 'example.com',
  certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abcd',
  // hostedZoneId optional — if omitted the zone is looked up by name
}

ACM cert must be in the same region as the API (HTTP API uses regional endpoints, not edge-optimized). If you need an edge cert, you'd typically front the API with CloudFront instead.

Handlers

Each handler is a normal APIGatewayProxyHandlerV2. The construct uses CDK's NodejsFunction, so esbuild compiles your TypeScript automatically — no build step needed.

// lambda/todos/list.ts
import { APIGatewayProxyHandlerV2 } from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';

const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler: APIGatewayProxyHandlerV2 = async () => {
  const out = await ddb.send(new ScanCommand({ TableName: process.env.TODOS_TABLE_NAME! }));
  return {
    statusCode: 200,
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ items: out.Items ?? [] }),
  };
};

Your CDK app should have @aws-sdk/* packages and aws-lambda types installed:

npm install --save-dev @types/aws-lambda
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

(@aws-sdk/* is external in the bundle — Lambda's Node 20 runtime already includes it.)

What about non-API stuff?

Just keep writing CDK normally. RoutedApi is one block in your stack — add SES identities, processing lambdas, EventBridge rules, S3 buckets, whatever, before or after it.

new RoutedApi(this, 'Api', { ... });

new lambda.Function(this, 'BackgroundProcessor', { ... });  // totally fine
new ses.EmailIdentity(this, 'EmailIdentity', { ... });      // also fine

Worked example

See example/ in this folder:

  • example/routes.json — the routes file
  • example/lambda/todos/{list,create,delete}.ts — handler files
  • example/example-stack.ts — the CDK stack