Skip to content

Jaypie

TypeScript framework for AWS Lambda and Express applications with CDK constructs and Datadog observability.

Stack: AWS Lambda, CDK, Express.js, Datadog, TypeScript, Node.js 22-25

Section Description
Core Concepts Handler lifecycle, errors, logging, environment
How-To Guides Step-by-step guides for common tasks
Packages API reference for each package
Experimental Unstable packages in development
Architecture Project structure, patterns
Contributing Development workflow

Jaypie is a TypeScript framework for AWS Lambda and Express applications with integrated CDK constructs and Datadog observability.

Layer Package Purpose
Infrastructure @jaypie/constructs CDK constructs for Lambda, API Gateway, CloudFront
Handlers @jaypie/express, @jaypie/lambda Handler wrappers with lifecycle and error formatting
Observability @jaypie/logger, @jaypie/datadog Structured logging, metrics
Testing @jaypie/testkit Mocks and matchers
Utilities @jaypie/kit, @jaypie/errors Type coercion, error classes
Terminal window
npm install jaypie
Terminal window
npm install jaypie @jaypie/constructs aws-cdk-lib constructs
Terminal window
npm install jaypie @jaypie/llm
Terminal window
npm install -D @jaypie/testkit @jaypie/eslint @jaypie/repokit vitest
import { expressHandler, log, NotFoundError } from "jaypie";
export default expressHandler(
async (req, res) => {
log.trace("[getUser] fetching");
const user = await db.users.findById(req.params.id);
if (!user) throw NotFoundError();
return { data: user };
},
{
name: "getUser",
secrets: ["MONGODB_URI"],
validate: [(req) => req.params.id],
setup: [async () => await connectDb()],
teardown: [async () => await disconnectDb()],
}
);
Option Type Purpose
name string Handler name for logging
secrets string[] Load from AWS Secrets Manager
validate Function[] Validation (throw or return false)
setup Function[] Run before handler
teardown Function[] Run after handler (always)
Return Status
object 200
null/undefined 204
true 201
Thrown error Error status
Class Status Use When
BadRequestError 400 Invalid input
UnauthorizedError 401 No/invalid auth
ForbiddenError 403 No permission
NotFoundError 404 Resource missing
InternalError 500 Server error
ConfigurationError 500 Missing config
BadGatewayError 502 External service error
UnavailableError 503 Service down
import { BadRequestError, NotFoundError } from "jaypie";
throw BadRequestError("Email required");
throw NotFoundError();
Level Use Case
trace Happy path
debug Unexpected but handled
info Significant events (rare)
warn Concerning situations
error Failures
import { log } from "jaypie";
log.trace("[functionName] starting");
log.var({ userId: "123" }); // Single key
log.debug("Cache miss");
log.error("Service failed");
import { force } from "jaypie";
force.boolean("true"); // true
force.number("42"); // 42
force.array("item"); // ["item"]
force.string(null, "default"); // "default"
import { isProductionEnv, isLocalEnv, isNodeTestEnv } from "jaypie";
if (isProductionEnv()) { /* production */ }
if (isLocalEnv()) { /* local dev */ }
if (isNodeTestEnv()) { /* testing */ }
import { uuid, sleep, cloneDeep } from "jaypie";
const id = uuid();
await sleep(1000);
const copy = cloneDeep(original);
Variable Purpose
PROJECT_ENV Environment: local, sandbox, production
PROJECT_KEY Project identifier
LOG_LEVEL trace, debug, info, warn, error
SECRET_* Secret references (fetched from Secrets Manager)
CDK_ENV_QUEUE_URL Default SQS queue
CDK_ENV_BUCKET Default S3 bucket

vitest.config.ts:

import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
setupFiles: ["@jaypie/testkit/testSetup"],
},
});

Mock Jaypie:

import { vi } from "vitest";
vi.mock("jaypie", async () => {
const testkit = await import("@jaypie/testkit/mock");
return testkit;
});

Custom Matchers:

expect(() => fn()).toThrowBadRequestError();
expect(() => fn()).toThrowNotFoundError();
expect(value).toMatchUuid();
import { JaypieLambda, JaypieApiGateway } from "@jaypie/constructs";
const api = new JaypieLambda(this, "Api", {
code: "../api/dist",
handler: "handler.handler",
secrets: [mongoSecret],
});
new JaypieApiGateway(this, "Gateway", {
handler: api,
host: "api.example.com",
zone: "example.com",
});
import Llm from "@jaypie/llm";
const response = await Llm.operate("What is 2+2?", {
model: "claude-sonnet-4",
});
// Streaming
for await (const chunk of Llm.stream("Tell me a story")) {
process.stdout.write(chunk.content || "");
}
Package Purpose
jaypie Main package: re-exports express, lambda, errors, kit, logger
@jaypie/express Express handler wrapper
@jaypie/lambda Lambda handler wrapper
@jaypie/errors JSON:API error classes
@jaypie/logger Structured logging
@jaypie/kit Utilities: force, uuid, sleep
Package Purpose
@jaypie/constructs CDK constructs with Datadog
@jaypie/llm LLM provider abstraction
Package Purpose
@jaypie/testkit Mocks and matchers
@jaypie/eslint ESLint configuration
@jaypie/repokit Repository tooling
Package Purpose
@jaypie/dynamodb DynamoDB single-table patterns
@jaypie/fabric Service handler adapters
@jaypie/fabricator Test data generation
@jaypie/mcp Model Context Protocol server
@jaypie/textract AWS Textract utilities
Goal Page
Understand handlers Handler Lifecycle
Build Express API Express on Lambda
Set up CDK CDK Infrastructure
Write tests Testing
Add LLM LLM Integration
CI/CD setup CI/CD