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

lambda-press

v1.2.0

Published

A framework to develop microservices using AWS Lambda.

Readme

  _                 _         _                               
 | |               | |       | |                             
 | | __ _ _ __ ___ | |__   __| | __ _      _ __  _ __ ___  ___ ___ 
 | |/ _` | '_ ` _ \| '_ \ / _` |/ _` |    | '_ \| '__/ _ \/ __/ __|
 | | (_| | | | | | | |_) | (_| | (_| |    | |_) | | |  __/\__ \__ \
 |_|\__,_|_| |_| |_|_.__/ \__,_|\__,_|    | .__/|_|  \___||___/___/
                                           | |
                                           |_|

npm version License Node.js PRs Welcome

A zero-dependency framework for building microservices on AWS Lambda.
Scaffold, develop locally, and deploy — without ever touching a config file.

Quick Start · CLI Reference · Router API · Request & Response · Deploy to Lambda · Debugging · Contributing


Table of Contents


What is lambda-press?

lambda-press is a lightweight CLI and runtime framework for building microservices meant to run on AWS Lambda. Each "app" in your project maps to a single Lambda function — independently deployable, independently scalable.

The local development server mirrors the Lambda routing behaviour exactly, so what works on your machine works in the cloud.

press create          →   scaffold a new project in seconds
npm start             →   local server with live reload on file change
npm run deploy        →   ship each app as its own Lambda function

No Express. No bloat. Zero runtime dependencies on Lambda.


Features

  • CLI scaffolding — interactive press create generates a full project in one command
  • Per-app Lambda functions — each app directory becomes an independent microservice
  • Built-in Router — HTTP routing with named params, multi-level paths, and middleware chains
  • Live reloadpress serve watches apps/ and commons/ and restarts on any file change
  • Per-app isolated servers — each app runs on its own port, mirroring Lambda's isolated runtime
  • Built-in test runnerpress test runs your Mocha test suite without any extra config
  • Zero Lambda runtime dependencies — the framework itself is bundled into the deployment zip
  • Convention over configuration — predictable file layout, no config files required

Installation

Install the CLI globally:

npm install -g lambda-press

Verify the install:

press --help

Quick Start

1. Create a new project

press create
  Welcome to lambda-press!

  Project name: my-api
  First app name: users

2. Move into the project and install dependencies

cd my-api
npm install

3. Start the local server

npm start
# or: press serve
  lambda-press local server running

    users                http://localhost:3000

4. Hit your endpoint

curl http://localhost:3000/
# {"message":"Hello from lambda-press!"}

Each app runs on its own port — add more apps under apps/ and they'll each get the next port automatically (3001, 3002, …). The server watches for file changes and restarts automatically.


Project Structure

my-api/
├── apps/
│   └── users/                  # one directory = one Lambda function
│       ├── routes.js           # URL routing
│       ├── middleware.js       # request/response middleware
│       ├── controllers.js      # request handlers
│       ├── models.js           # data models
│       └── handler.js          # Lambda entry point (auto-generated)
├── commons/
│   ├── utils.js                # shared utilities
│   └── constants.js            # shared constants
├── test/
│   └── index.js                # test suite (mocha)
└── package.json

Adding a second app is just adding a new directory under apps/. The server picks it up automatically on the next restart. Each app gets its own port, assigned sequentially from the base port.

apps/
├── users/       →   http://localhost:3000
├── orders/      →   http://localhost:3001
└── products/    →   http://localhost:3002

Writing Your App

Routes

apps/<name>/routes.js is the entry point for an app. Register routes on a Router instance and export it.

const { Router } = require('lambda-press');
const controllers = require('./controllers');
const { logger } = require('./middleware');

const router = new Router();

router.use(logger);

router.get('/', controllers.list);
router.get('/:id', controllers.get);
router.post('/', controllers.create);
router.put('/:id', controllers.update);
router.delete('/:id', controllers.remove);

module.exports = router;

| Method | Router call | |--------|-------------| | GET | router.get(path, ...handlers) | | POST | router.post(path, ...handlers) | | PUT | router.put(path, ...handlers) | | PATCH | router.patch(path, ...handlers) | | DELETE | router.delete(path, ...handlers) |


Middleware

apps/<name>/middleware.js exports functions with the (req, res, next) signature. Register them with router.use() and they run before every route handler in that app.

const logger = (req, res, next) => {
    console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
    next();
};

const auth = (req, res, next) => {
    const token = req.get('authorization');
    if (!token) {
        res.status(401).json({ error: 'Unauthorized' });
        return;
    }
    next();
};

module.exports = { logger, auth };

You can also pass middleware inline on individual routes:

router.get('/admin', auth, controllers.admin);

Pass an error to next(err) to immediately send a 500 response and stop the chain:

const validate = (req, res, next) => {
    if (!req.body.name) return next(new Error('name is required'));
    next();
};

Controllers

apps/<name>/controllers.js exports handler functions. Each receives the augmented req and res objects.

const list = async (req, res) => {
    // req.query  — parsed query string     { page: '1' }
    // req.params — named URL params        { id: '42' }
    // req.body   — parsed request body     { name: 'Alice' }
    // req.get()  — case-insensitive header lookup

    const users = await getUsers(req.query);
    res.json(users);
};

const get = async (req, res) => {
    const user = await getUserById(req.params.id);
    if (!user) {
        res.status(404).json({ error: 'Not found' });
        return;
    }
    res.json(user);
};

const create = async (req, res) => {
    const user = await createUser(req.body);
    res.status(201).json(user);
};

const update = async (req, res) => {
    const apiKey = req.get('x-api-key');   // case-insensitive header read
    if (!apiKey) {
        res.status(401).json({ error: 'API key required' });
        return;
    }
    const updated = await updateUser(req.params.id, req.body);
    res.json(updated);
};

module.exports = { list, get, create, update };

| Helper | Description | |--------|-------------| | res.json(data) | Send a JSON response (Content-Type: application/json) | | res.send(data) | Send a string, or auto-detect JSON for objects | | res.status(code) | Set the HTTP status code — returns res for chaining | | res.set(header, value) | Set a response header — returns res for chaining | | res.type(contentType) | Set Content-Type — returns res for chaining |

Example chains:

res.status(422).json({ error: 'Validation failed', fields: ['email'] });
res.status(201).set('Location', '/users/42').json(user);

Models

apps/<name>/models.js is where your data layer lives. No conventions enforced — wire up any database, ORM, or external API here.

const { DB_URL } = require('../../commons/constants');

const findAll = async (filters) => {
    // query your database
};

const findById = async (id) => {
    // query your database
};

module.exports = { findAll, findById };

Handler

apps/<name>/handler.js is the Lambda entry point. press create generates it automatically — you rarely need to touch it.

const { createHandler } = require('lambda-press');
const router = require('./routes');

module.exports.handler = createHandler(router);

createHandler accepts both API Gateway payload formats (v1 REST API and v2 HTTP API) and translates them into the same req/res interface your routes already use.


Commons

Shared code goes in the commons/ directory — reusable across all apps.

commons/
├── utils.js      # helper functions
└── constants.js  # environment config, shared values
// commons/constants.js
module.exports = {
    DB_URL: process.env.DB_URL || 'mongodb://localhost:27017/mydb',
    PAGE_SIZE: 20,
};
// commons/utils.js
const paginate = (items, page, size) =>
    items.slice((page - 1) * size, page * size);

module.exports = { paginate };

CLI Reference

| Command | Description | |---------|-------------| | press create | Interactively scaffold a new project | | press serve | Start each app on its own port from 3000, with live reload | | press serve --port 8000 | Start from a custom base port (8000, 8001, …) | | press test | Run the project test suite with Mocha | | press deploy | Deploy all apps to AWS Lambda | | press logs <app> | Fetch recent CloudWatch logs for a deployed app | | press logs <app> --tail | Follow logs in real time (polls every 2 s) | | press invoke <app> | Invoke a deployed function and print the response | | press invoke <app> --data '<json>' | Invoke with a custom event payload |

Shared options for deploy, logs, and invoke:

--access-key <key>    AWS access key ID
--secret-key <secret> AWS secret access key
--region <region>     AWS region (default: us-east-1)

Additional serve options:

--port <port>         Base port for the first app (default: 3000)

Additional logs options:

--tail, -f            Follow — poll for new entries every 2 s
--since <minutes>     How far back to look (default: 60)
--filter <pattern>    Only show lines matching this pattern

Additional deploy options:

--role <arn>          IAM execution role ARN (auto-created if omitted)

Additional invoke options:

--data <json>         Event payload (default: GET / API Gateway v2 event)

Router API

lambda-press exports two values:

const { Router, createHandler } = require('lambda-press');

Route registration

const router = new Router();

router.get(path, ...handlers)
router.post(path, ...handlers)
router.put(path, ...handlers)
router.patch(path, ...handlers)
router.delete(path, ...handlers)
  • path — a string like '/', '/users', '/users/:id', or '/posts/:postId/comments/:commentId'
  • handlers — one or more (req, res, next) => void functions executed in order

Middleware registration

router.use(fn)

Runs fn before every route handler in the router. Call next() to advance, next(err) to abort with a 500.

Named URL parameters

Prefix any path segment with : to capture it as a named parameter. Parameters are available on req.params.

router.get('/users/:id/posts/:postId', (req, res) => {
    const { id, postId } = req.params;
    res.json({ userId: id, postId });
});

Path matching rules

| Rule | Example | |------|---------| | Exact segments match literally | /users only matches /users | | :param matches any non-empty segment | /:id matches /42 but not / | | Trailing slashes are normalized | /users/ and /users match the same routes | | Multi-level params work | /posts/:postId/comments/:commentId |


Request & Response

Request object (req)

| Property | Type | Description | |----------|------|-------------| | req.method | string | HTTP method — 'GET', 'POST', etc. | | req.url | string | Raw URL including query string | | req.path | string | Pathname without query string — /users/42 | | req.params | object | Named URL parameters — { id: '42' } | | req.query | object | Parsed query string — { page: '1', sort: 'asc' } | | req.body | object \| string \| null | Parsed request body (see below) | | req.headers | object | All headers, keys lowercased | | req.hostname | string | Host without port — 'example.com' | | req.protocol | string | 'http' or 'https' | | req.get(name) | function | Case-insensitive header lookup |

Reading headers:

Always use req.get() for header access — it is case-insensitive and works identically in local and Lambda environments:

const apiKey    = req.get('x-api-key');       // custom header
const authToken = req.get('authorization');    // Authorization: Bearer ...
const ct        = req.get('content-type');     // Content-Type

Body parsing:

| Content-Type | req.body value | |----------------|-----------------| | application/json | Parsed object — { name: 'Alice' } | | application/x-www-form-urlencoded | Parsed object — { name: 'Alice' } | | anything else | Raw string | | empty body | null |

Response object (res)

| Helper | Description | |--------|-------------| | res.json(data) | Send JSON (Content-Type: application/json) | | res.send(data) | Send string, or auto-detect JSON for objects | | res.status(code) | Set status code — chainable | | res.set(header, value) | Set a response header — chainable | | res.get(header) | Read a response header | | res.type(contentType) | Set Content-Type — chainable |

// Chaining
res.status(201).set('Location', '/users/42').json(user);
res.status(422).json({ error: 'Validation failed', fields: ['email'] });

// Plain text
res.status(200).type('text/plain').send('OK');

Deploying to AWS Lambda

Each app scaffolded by press create already includes a handler.js ready for Lambda. The press deploy command zips each app and creates or updates its Lambda function automatically.

Quick deploy

# Uses ~/.aws/credentials (default AWS profile)
press deploy

# Explicit credentials
press deploy --access-key AKIA... --secret-key wJal... --region ap-south-1

# Bring your own IAM execution role
press deploy --role arn:aws:iam::123456789012:role/my-role
  Deploying to AWS Lambda  region: ap-south-1

  Resolving IAM execution role... done

  users                    created
    https://abc123.lambda-url.ap-south-1.on.aws/
  orders                   updated
    https://def456.lambda-url.ap-south-1.on.aws/

  Done

How it works

| Step | What happens | |------|-------------| | IAM role | Finds or creates lambda-press-execution-role with AWSLambdaBasicExecutionRole (skip with --role) | | Zip | Bundles apps/<name>/, commons/, framework runtime, and node_modules/ | | Create | Calls CreateFunction if the function does not exist, then waits until active | | Update | Calls UpdateFunctionCode then waits until ready before applying config changes | | Function URL | Creates a public HTTPS URL for the function (AuthType: NONE) |

Credential resolution order

  1. --access-key / --secret-key CLI flags
  2. AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY environment variables
  3. ~/.aws/credentials — default AWS profile

Function naming

Functions are named <project-name>-<app-name>, matching the name field in your package.json.

my-api/apps/users    →   Lambda: my-api-users
my-api/apps/orders   →   Lambda: my-api-orders

Permissions needed to deploy

The AWS credentials used must have the following permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LambdaDeployment",
      "Effect": "Allow",
      "Action": [
        "lambda:GetFunction",
        "lambda:CreateFunction",
        "lambda:UpdateFunctionCode",
        "lambda:UpdateFunctionConfiguration",
        "lambda:GetFunctionUrlConfig",
        "lambda:CreateFunctionUrlConfig",
        "lambda:AddPermission",
        "lambda:InvokeFunction"
      ],
      "Resource": "arn:aws:lambda:*:*:function:*"
    },
    {
      "Sid": "IAMRoleManagement",
      "Effect": "Allow",
      "Action": [
        "iam:GetRole",
        "iam:CreateRole",
        "iam:AttachRolePolicy",
        "iam:PassRole"
      ],
      "Resource": "arn:aws:iam::*:role/lambda-press-*"
    },
    {
      "Sid": "CloudWatchLogs",
      "Effect": "Allow",
      "Action": [
        "logs:FilterLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:log-group:/aws/lambda/*"
    }
  ]
}

| Permission | Why it's needed | |---|---| | lambda:GetFunction | Check whether a function exists and poll update status | | lambda:CreateFunction | Create a new Lambda function on first deploy | | lambda:UpdateFunctionCode | Upload a new zip to an existing function | | lambda:UpdateFunctionConfiguration | Correct the handler reference on existing functions | | lambda:GetFunctionUrlConfig | Check whether a Function URL already exists | | lambda:CreateFunctionUrlConfig | Create a public HTTPS Function URL after deploy | | lambda:AddPermission | Grant public InvokeFunctionUrl access to the Function URL | | lambda:InvokeFunction | Invoke a deployed function via press invoke | | iam:GetRole | Look up the lambda-press-execution-role before trying to create it | | iam:CreateRole | Create the execution role when it doesn't exist | | iam:AttachRolePolicy | Attach AWSLambdaBasicExecutionRole to the created role | | iam:PassRole | Required by AWS when CreateFunction assigns a role to a function | | logs:FilterLogEvents | Read CloudWatch log events via press logs |

If you supply your own role via --role <arn>, the three iam:* actions are not needed.
Scope the IAMRoleManagement statement to that specific ARN to follow least-privilege.


Debugging

View CloudWatch logs

# Last 60 minutes of logs
press logs users

# Follow in real time
press logs users --tail

# Last 10 minutes, errors only
press logs users --since 10 --filter ERROR
  my-api-users  (following)

  2026-04-26T11:02:01.000Z  START RequestId: abc-123 Version: $LATEST
  2026-04-26T11:02:01.005Z  2026-04-26T11:02:01.005Z GET /
  2026-04-26T11:02:01.008Z  END RequestId: abc-123
  2026-04-26T11:02:01.009Z  REPORT RequestId: abc-123 Duration: 3.21 ms Billed Duration: 4 ms ...

  Watching for new logs... (Ctrl+C to stop)

Invoke a deployed function

# Default GET / event (API Gateway v2 format)
press invoke users

# Custom event payload
press invoke users --data '{"version":"2.0","rawPath":"/users/42","requestContext":{"http":{"method":"GET"}},"body":null}'
  Invoking my-api-users

  Status   200

  Response
    {
      "statusCode": 200,
      "headers": { "content-type": "application/json" },
      "body": { "message": "Hello from lambda-press!" },
      "isBase64Encoded": false
    }

  Execution logs
    START RequestId: abc-123 Version: $LATEST
    2026-04-26T11:02:01.005Z GET /users/42
    END RequestId: abc-123
    REPORT RequestId: abc-123 Duration: 3.21 ms Billed Duration: 4 ms Memory Size: 256 MB Max Memory Used: 64 MB

Contributing

Contributions are welcome.

# clone
git clone https://github.com/trishantpahwa/lambda-press.git
cd lambda-press

# install dev dependencies
npm install

# run tests
npm test

Please open an issue before submitting a pull request for large changes.


License

Apache 2.0 — © Trishant Pahwa