Skip to content

feat: add Express and Lambda adapters#283

Open
ryo8000 wants to merge 1 commit into
mainfrom
feature/lambda
Open

feat: add Express and Lambda adapters#283
ryo8000 wants to merge 1 commit into
mainfrom
feature/lambda

Conversation

@ryo8000

@ryo8000 ryo8000 commented Mar 6, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Application can now be deployed to AWS Lambda
    • Added build scripts for Lambda function and layer packaging
  • Bug Fixes

    • Updated network error handling for Lambda environments to return HTTP 400
  • Chores

    • Refactored codebase with adapter architecture pattern
    • Added AWS Lambda type definitions support

@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown

Walkthrough

This PR refactors the project structure to introduce runtime adapters (Express and Lambda), relocates core modules under src/core/, updates all entry points and import paths accordingly, and adds a Lambda handler for serverless execution.

Changes

Cohort / File(s) Summary
Configuration & Entry Points
.claude/settings.json, .github/workflows/ci.yml, Dockerfile, nodemon.json, package.json
Updated startup command from node dist/server.js to node dist/adapters/express/index.js; updated package.json main entry point and added build scripts for Lambda function and layer artifacts.
Express Adapter
src/adapters/express/app.ts, src/adapters/express/index.ts
Created Express adapter with updated imports from core modules; app.ts now imports middleware and routes from ../../core/ instead of local relative paths.
Lambda Adapter
src/adapters/lambda/index.ts
New Lambda handler that proxies API Gateway v2 events to a locally running Express server on a loopback interface; includes hop-by-hop header filtering, port caching on cold start, request/response translation, and structured error handling.
Core Modules - Routes
src/core/routes/base64.ts, src/core/routes/basic-auth.ts, src/core/routes/error.ts, src/core/routes/index.ts, src/core/routes/redirect.ts, src/core/routes/shutdown.ts, src/core/routes/status.ts
Updated import paths from one level up to two levels; error.ts adds runtime-specific behavior: returns 400 on Lambda for network endpoint instead of socket destruction.
Core Modules - Middlewares
src/core/middlewares/delay.ts
Updated import path from ../utils/ to ../../utils/.
Documentation
README.md, docs/DEVELOPMENT_GUIDE.md, AGENTS.md
Updated startup command examples and documented new runtime-adapters directory structure with express and lambda entries; updated error endpoint behavior for Lambda.
Tests
tests/ut/app.test.ts, tests/ut/env.test.ts, tests/ut/middlewares/delay.test.ts, tests/ut/middlewares/logger.test.ts, tests/ut/routes/base64.test.ts, tests/ut/routes/basic-auth.test.ts, tests/ut/routes/error.test.ts, tests/ut/routes/index.test.ts, tests/ut/routes/mirror.test.ts, tests/ut/routes/redirect.test.ts, tests/ut/routes/request.test.ts, tests/ut/routes/shutdown.test.ts, tests/ut/routes/status.test.ts, tests/ut/routes/uuid.test.ts
Updated import paths to reference core module locations; error.test.ts adds test coverage for runtime-specific Lambda behavior with environment setup/teardown.

Sequence Diagram(s)

sequenceDiagram
    participant AGW as API Gateway v2
    participant LH as Lambda Handler
    participant ES as Express Server
    participant HTTP as HTTP Client

    AGW->>LH: APIGatewayProxyEventV2
    LH->>LH: Cold start: start Express on loopback
    LH->>LH: Convert event to HTTP request
    LH->>LH: Filter hop-by-hop headers
    LH->>ES: HTTP request to 127.0.0.1:port
    ES->>ES: Route & process request
    ES-->>LH: HTTP response
    LH->>LH: Aggregate headers & cookies
    LH->>LH: Build APIGatewayProxyStructuredResultV2
    LH-->>AGW: Structured result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 Adapters bloom in organized rows,
Express and Lambda in structured flows,
Core modules nested, imports refined,
Serverless hops through the code aligned!
A rabbit-built home for runtimes to play. 🌿✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Express and Lambda adapters' accurately summarizes the main changes: introducing Express and Lambda runtime adapters with a new src/adapters/ directory structure and updated entry points across configuration files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/lambda

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces adapters for running the HTTP Playground server in different environments, specifically Express and AWS Lambda. It involves refactoring the project structure to separate core application logic from adapter-specific code, enabling the application to run as a Lambda function. The changes include creating new adapter entry points, updating build scripts, and modifying existing code to support both environments.

Highlights

  • Adapters: Introduced adapters for running the application in different environments (Express and Lambda).
  • Code Reorganization: Refactored the project structure to separate core logic from adapter-specific code.
  • Lambda Support: Added support for running the application as an AWS Lambda function.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .claude/settings.json
    • Updated the allowed bash command to use the new Express adapter entry point.
  • AGENTS.md
    • Updated the documentation to reflect the new Express adapter entry point.
  • Dockerfile
    • Updated the Dockerfile to use the new Express adapter entry point.
  • README.md
    • Updated the README to reflect the new Express adapter entry point and added a note about the network error simulation in Lambda.
  • docs/DEVELOPMENT_GUIDE.md
    • Updated the development guide to reflect the new project structure and adapter entry points.
  • nodemon.json
    • Updated the nodemon configuration to use the new Express adapter entry point.
  • package.json
    • Updated the main entry point to the new Express adapter, added build scripts for Lambda, and added a dependency for AWS Lambda types.
  • src/adapters/lambda/index.ts
    • Added a new Lambda adapter entry point.
  • src/app.ts
    • Moved and renamed to src/adapters/express/app.ts, updated imports to reflect the new project structure.
  • src/env.ts
    • Moved and renamed to src/core/env.ts.
  • src/logger.ts
    • Moved and renamed to src/core/logger.ts.
  • src/middlewares/delay.ts
    • Moved and renamed to src/core/middlewares/delay.ts, updated imports to reflect the new project structure.
  • src/middlewares/logger.ts
    • Moved and renamed to src/core/middlewares/logger.ts.
  • src/routes/base64.ts
    • Moved and renamed to src/core/routes/base64.ts, updated imports to reflect the new project structure.
  • src/routes/basic-auth.ts
    • Moved and renamed to src/core/routes/basic-auth.ts, updated imports to reflect the new project structure.
  • src/routes/error.ts
    • Moved and renamed to src/core/routes/error.ts, updated imports to reflect the new project structure, and added Lambda-specific error handling for network errors.
  • src/routes/index.ts
    • Moved and renamed to src/core/routes/index.ts, updated imports to reflect the new project structure.
  • src/routes/mirror.ts
    • Moved and renamed to src/core/routes/mirror.ts.
  • src/routes/redirect.ts
    • Moved and renamed to src/core/routes/redirect.ts, updated imports to reflect the new project structure.
  • src/routes/request.ts
    • Moved and renamed to src/core/routes/request.ts.
  • src/routes/shutdown.ts
    • Moved and renamed to src/core/routes/shutdown.ts, updated imports to reflect the new project structure.
  • src/routes/status.ts
    • Moved and renamed to src/core/routes/status.ts, updated imports to reflect the new project structure.
  • src/routes/uuid.ts
    • Moved and renamed to src/core/routes/uuid.ts.
  • src/server.ts
    • Moved and renamed to src/adapters/express/index.ts, updated imports to reflect the new project structure.
  • tests/ut/app.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/env.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/middlewares/delay.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/middlewares/logger.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/base64.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/basic-auth.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/error.test.ts
    • Updated imports to reflect the new project structure and added tests for Lambda-specific error handling.
  • tests/ut/routes/index.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/mirror.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/redirect.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/request.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/shutdown.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/status.test.ts
    • Updated imports to reflect the new project structure.
  • tests/ut/routes/uuid.test.ts
    • Updated imports to reflect the new project structure.
  • yarn.lock
    • Updated yarn.lock to include the new @types/aws-lambda dependency.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/ci.yml
Activity
  • No reviewer activity detected.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


Adapters bridge the divide, Express and Lambda, side by side. Code's essence, a common core, Deployed with ease, and nothing more.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant architectural improvement by adding support for AWS Lambda through an adapter pattern, separating the core application logic from the runtime environment. The changes are generally well-structured and include necessary updates to documentation and build scripts. I've identified a few areas for improvement, primarily in the new Lambda adapter and build scripts, focusing on error handling, binary data support, and build efficiency. Additionally, there's a minor inconsistency in one of the new tests that should be addressed.

Comment on lines +130 to +136
} catch {
return {
statusCode: 500,
body: JSON.stringify({ error: { message: 'Internal server error' } }),
headers: { 'content-type': 'application/json' },
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This catch block swallows the error object, which will make debugging very difficult in case of a failure. You should capture the error and log it for better observability.

  } catch (error) {
    console.error('Error in Lambda handler:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: { message: 'Internal server error' } }),
      headers: { 'content-type': 'application/json' },
    };
  }

Comment thread package.json
"license": "MIT",
"scripts": {
"build": "tsc",
"build:lambda:function": "yarn build && cp package.json dist/ && zip -r lambda-function.zip dist/",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The build:lambda:function script can be improved for efficiency and correctness.

  1. It includes the dist directory itself in the zip archive, which means your Lambda handler path will need to be dist/adapters/lambda/index.handler. It's generally better to zip the contents of the dist directory.
  2. It packages the Express adapter, which is not needed for the Lambda function, increasing the package size.

A simple improvement for the zip structure is to cd into the directory first.

Suggested change
"build:lambda:function": "yarn build && cp package.json dist/ && zip -r lambda-function.zip dist/",
"build:lambda:function": "yarn build && cp package.json dist/ && (cd dist && zip -r ../lambda-function.zip .)",

Comment thread package.json
"scripts": {
"build": "tsc",
"build:lambda:function": "yarn build && cp package.json dist/ && zip -r lambda-function.zip dist/",
"build:lambda:layer": "mkdir -p lambda-layer/nodejs && cp -r node_modules lambda-layer/nodejs && cd lambda-layer && zip -r ../lambda-layer.zip . && cd .. && rm -rf lambda-layer",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The build:lambda:layer script copies the entire node_modules directory, which may include development dependencies, making the Lambda layer larger than necessary. Consider installing only production dependencies for the layer. Also, using cd ... && cd .. can be risky if a command fails; using a subshell (...) is safer.

Suggested change
"build:lambda:layer": "mkdir -p lambda-layer/nodejs && cp -r node_modules lambda-layer/nodejs && cd lambda-layer && zip -r ../lambda-layer.zip . && cd .. && rm -rf lambda-layer",
"build:lambda:layer": "mkdir -p lambda-layer/nodejs && cp -r node_modules lambda-layer/nodejs && (cd lambda-layer && zip -r ../lambda-layer.zip .) && rm -rf lambda-layer",

Comment on lines +124 to +129
return {
statusCode,
headers: responseHeaders,
body: rawBody.toString('utf-8'),
...(cookies.length > 0 && { cookies }),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The response body is always converted to a utf-8 string. This will corrupt binary responses. The handler should check the Content-Type of the response and if it's binary, base64 encode the body and set isBase64Encoded: true in the response object. While the current application may not serve binary files, this would make the adapter more robust.

    const contentType = responseHeaders['content-type'] || '';
    const isText = /^(text\/|application\/json)/.test(contentType);
    return {
      statusCode,
      headers: responseHeaders,
      body: rawBody.toString(isText ? 'utf8' : 'base64'),
      isBase64Encoded: !isText,
      ...(cookies.length > 0 && { cookies }),
    };

Comment thread src/core/routes/error.ts
Comment on lines +11 to +17
if (process.env['RUNTIME'] === 'lambda') {
res
.status(400)
.json({
error: { message: 'Network error simulation is not supported in this environment.' },
});
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The associated test for this logic in tests/ut/routes/error.test.ts expects a 500 status code, but the implementation here returns 400. The README.md also states it returns 400. A 400 Bad Request is more appropriate since the client is requesting an action not supported in the current environment. Please update the test to expect a 400 status code to align with the implementation and documentation.

process.env['RUNTIME'] = 'lambda';
try {
const response = await request(app)[method]('/error/network');
expect(response.status).toBe(500);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test expects a 500 status code, but the implementation in src/core/routes/error.ts returns a 400 for the /error/network endpoint in a Lambda environment. The README.md also states it returns 400. A 400 Bad Request is more appropriate here since the client is requesting an action not supported in the current environment. This test should be updated to expect a 400.

Suggested change
expect(response.status).toBe(500);
expect(response.status).toBe(400);

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/core/routes/error.ts (1)

10-22: ⚠️ Potential issue | 🔴 Critical

Test expects status 500 but implementation returns 400.

The test in tests/ut/routes/error.test.ts (line 35) expects response.status to be 500 for the Lambda runtime path, but the implementation returns 400. This mismatch will cause the test to fail.

The implementation at line 13 of src/core/routes/error.ts explicitly returns:

.status(400)

But the test at line 35 asserts:

expect(response.status).toBe(500);

Fix either the test or the implementation to match:

  • Update test: Change line 35 to expect(response.status).toBe(400) and update the test description to reflect 400 status
  • Update implementation: Change line 13 to .status(500) if 500 is the intended status code
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/routes/error.ts` around lines 10 - 22, The Lambda-runtime branch in
the error route returns status 400 but tests expect 500; inside the
errorRouter.all('/network', (req, res) => { ... }) block where it checks
process.env['RUNTIME'] === 'lambda', change the response status call from
.status(400) to .status(500) so the Lambda path returns HTTP 500 (adjust the
JSON message if needed to keep context).
AGENTS.md (1)

25-29: ⚠️ Potential issue | 🟡 Minor

Update AGENTS.md to reflect current file structure.

The documentation at lines 25-29 references outdated file paths. The actual codebase uses:

  • src/adapters/express/app.ts (not src/app.ts)
  • src/adapters/express/index.ts (not src/server.ts)
  • src/core/env.ts (not src/env.ts)
  • src/core/logger.ts (not src/logger.ts)

Also update line 32 to reference src/core/routes/ instead of src/routes/.

📝 Proposed documentation update
 **Core Structure**:
-- `src/app.ts` - Main Express application setup with middleware chain and route registration
-- `src/server.ts` - HTTP server startup with graceful shutdown handling
-- `src/env.ts` - Environment variable validation and configuration
-- `src/logger.ts` - Pino logger setup with development/production configurations
+- `src/adapters/express/app.ts` - Main Express application setup with middleware chain and route registration
+- `src/adapters/express/index.ts` - HTTP server startup with graceful shutdown handling
+- `src/core/env.ts` - Environment variable validation and configuration
+- `src/core/logger.ts` - Pino logger setup with development/production configurations
 
 **Route Structure**:
-- All routes in `src/routes/` export an Express Router
-- Registered in `src/app.ts` with path prefixes
+- All routes in `src/core/routes/` export an Express Router
+- Registered in `src/adapters/express/app.ts` with path prefixes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 25 - 29, Update AGENTS.md to reflect the current
repository layout by replacing the outdated paths: change `src/app.ts` to
`src/adapters/express/app.ts`, `src/server.ts` to
`src/adapters/express/index.ts`, `src/env.ts` to `src/core/env.ts`, and
`src/logger.ts` to `src/core/logger.ts`; also update the routes reference at
line 32 from `src/routes/` to `src/core/routes/`. Ensure the Core Structure
bullet list and the single "routes" reference are edited consistently so the doc
matches the codebase.
🧹 Nitpick comments (2)
src/adapters/lambda/index.ts (2)

130-136: Consider logging errors for observability.

The catch block silently swallows errors without logging, making debugging Lambda invocations difficult. CloudWatch logs would benefit from error details.

♻️ Proposed enhancement to add error logging
-  } catch {
+  } catch (err) {
+    console.error('Lambda handler error:', err);
     return {
       statusCode: 500,
       body: JSON.stringify({ error: { message: 'Internal server error' } }),
       headers: { 'content-type': 'application/json' },
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapters/lambda/index.ts` around lines 130 - 136, The catch block that
returns the 500 response currently swallows the exception; change it to capture
the thrown error (e.g., catch (err)) and log the error and relevant context
(such as the incoming event or request id) to CloudWatch using the existing
logger or console.error before returning the JSON 500 response so failures are
observable; update the catch in the function that builds the Lambda response
(the block that returns statusCode: 500 and body JSON) to include the error
logging statement.

124-129: Binary responses will be corrupted.

rawBody.toString('utf-8') assumes all responses are UTF-8 text. Binary responses (e.g., images) will be corrupted. API Gateway v2 supports isBase64Encoded for binary content.

♻️ Optional enhancement for binary response support
+    // Detect if response is likely binary based on content-type
+    const contentType = responseHeaders['content-type'] ?? '';
+    const isBinary = /^(image|audio|video|application\/octet-stream)/i.test(contentType);
+
     return {
       statusCode,
       headers: responseHeaders,
-      body: rawBody.toString('utf-8'),
+      body: isBinary ? rawBody.toString('base64') : rawBody.toString('utf-8'),
+      isBase64Encoded: isBinary,
       ...(cookies.length > 0 && { cookies }),
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapters/lambda/index.ts` around lines 124 - 129, The code always
converts rawBody with rawBody.toString('utf-8'), which corrupts binary
responses; detect whether the response is textual (e.g., Content-Type header
includes "text/" or "application/json" or a "charset=utf-8") and only then use
rawBody.toString('utf-8'), otherwise set body = rawBody.toString('base64') and
add isBase64Encoded: true to the returned object; update the return construction
that uses rawBody, responseHeaders, statusCode, and cookies to conditionally
encode and include isBase64Encoded accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/DEVELOPMENT_GUIDE.md`:
- Around line 9-19: The docs tree in DEVELOPMENT_GUIDE.md is inconsistent with
the repository convention; update the PR so the authoritative layout matches the
existing codebase by reverting or editing the docs to document the canonical
files: ensure application setup lives in src/app.ts (Express app with middleware
and route registration), routes live under src/routes/* exporting Express
Router, logger configuration is described in src/logger.ts (dev/prod configs),
server startup and graceful shutdown are documented for src/server.ts, and
environment variables (ENABLE_SHUTDOWN, LOG_LEVEL, MAX_DELAY, NODE_ENV, ORIGIN,
PORT, KEEP_ALIVE_TIMEOUT, HEADERS_TIMEOUT, REQUEST_TIMEOUT) are described in
src/env.ts; reference these filenames (src/app.ts, src/server.ts, src/env.ts,
src/logger.ts, src/routes) when making the doc changes so the guide and repo
conventions are in sync.

In `@package.json`:
- Line 12: The current "build:lambda:layer" npm script copies the entire
node_modules (including devDependencies) into the Lambda layer; change it to
install only production dependencies into the layer directory instead. Update
the script so it creates lambda-layer/nodejs and runs a production install into
that folder (for example using npm ci --only=production --prefix
lambda-layer/nodejs or npm install --production --prefix lambda-layer/nodejs),
then zip and clean up as before; ensure you remove the plain cp -r node_modules
step in the "build:lambda:layer" script so devDependencies like
Jest/ESLint/TypeScript are not included.

In `@src/adapters/lambda/index.ts`:
- Around line 26-37: The Lambda adapter never sets the RUNTIME env var so
requests hit the socket-destroy path; before creating or starting the HTTP
server (i.e., before the listeningPromise / createServer(app) / server.listen
call) set process.env['RUNTIME'] = 'lambda' so the error route in
src/core/routes/error.ts sees RUNTIME==='lambda' and returns the safe 400
response instead of attempting req.socket.destroy(); ensure this assignment runs
synchronously prior to any server creation/listen logic.

In `@tests/ut/routes/error.test.ts`:
- Around line 31-44: The test's assertion and description are wrong for the
lambda runtime case: update the it(...) description and the expect(...)
assertion in the test for the '/error/network' route (inside the test case that
sets process.env['RUNTIME']='lambda') to expect a 400 status code instead of
500; leave the conditional body check for non-head methods unchanged (it already
asserts the error message), and ensure the finally block still deletes
process.env['RUNTIME'] so cleanup remains intact.

---

Outside diff comments:
In `@AGENTS.md`:
- Around line 25-29: Update AGENTS.md to reflect the current repository layout
by replacing the outdated paths: change `src/app.ts` to
`src/adapters/express/app.ts`, `src/server.ts` to
`src/adapters/express/index.ts`, `src/env.ts` to `src/core/env.ts`, and
`src/logger.ts` to `src/core/logger.ts`; also update the routes reference at
line 32 from `src/routes/` to `src/core/routes/`. Ensure the Core Structure
bullet list and the single "routes" reference are edited consistently so the doc
matches the codebase.

In `@src/core/routes/error.ts`:
- Around line 10-22: The Lambda-runtime branch in the error route returns status
400 but tests expect 500; inside the errorRouter.all('/network', (req, res) => {
... }) block where it checks process.env['RUNTIME'] === 'lambda', change the
response status call from .status(400) to .status(500) so the Lambda path
returns HTTP 500 (adjust the JSON message if needed to keep context).

---

Nitpick comments:
In `@src/adapters/lambda/index.ts`:
- Around line 130-136: The catch block that returns the 500 response currently
swallows the exception; change it to capture the thrown error (e.g., catch
(err)) and log the error and relevant context (such as the incoming event or
request id) to CloudWatch using the existing logger or console.error before
returning the JSON 500 response so failures are observable; update the catch in
the function that builds the Lambda response (the block that returns statusCode:
500 and body JSON) to include the error logging statement.
- Around line 124-129: The code always converts rawBody with
rawBody.toString('utf-8'), which corrupts binary responses; detect whether the
response is textual (e.g., Content-Type header includes "text/" or
"application/json" or a "charset=utf-8") and only then use
rawBody.toString('utf-8'), otherwise set body = rawBody.toString('base64') and
add isBase64Encoded: true to the returned object; update the return construction
that uses rawBody, responseHeaders, statusCode, and cookies to conditionally
encode and include isBase64Encoded accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 01a3955f-3f83-487b-99e1-7e064f92b2f7

📥 Commits

Reviewing files that changed from the base of the PR and between c25ea2d and 2b7a15c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (39)
  • .claude/settings.json
  • .github/workflows/ci.yml
  • AGENTS.md
  • Dockerfile
  • README.md
  • docs/DEVELOPMENT_GUIDE.md
  • nodemon.json
  • package.json
  • src/adapters/express/app.ts
  • src/adapters/express/index.ts
  • src/adapters/lambda/index.ts
  • src/core/env.ts
  • src/core/logger.ts
  • src/core/middlewares/delay.ts
  • src/core/middlewares/logger.ts
  • src/core/routes/base64.ts
  • src/core/routes/basic-auth.ts
  • src/core/routes/error.ts
  • src/core/routes/index.ts
  • src/core/routes/mirror.ts
  • src/core/routes/redirect.ts
  • src/core/routes/request.ts
  • src/core/routes/shutdown.ts
  • src/core/routes/status.ts
  • src/core/routes/uuid.ts
  • tests/ut/app.test.ts
  • tests/ut/env.test.ts
  • tests/ut/middlewares/delay.test.ts
  • tests/ut/middlewares/logger.test.ts
  • tests/ut/routes/base64.test.ts
  • tests/ut/routes/basic-auth.test.ts
  • tests/ut/routes/error.test.ts
  • tests/ut/routes/index.test.ts
  • tests/ut/routes/mirror.test.ts
  • tests/ut/routes/redirect.test.ts
  • tests/ut/routes/request.test.ts
  • tests/ut/routes/shutdown.test.ts
  • tests/ut/routes/status.test.ts
  • tests/ut/routes/uuid.test.ts

Comment thread docs/DEVELOPMENT_GUIDE.md
Comment on lines +9 to +19
│ ├── adapters # Runtime-specific entry points
│ │ ├── express # Express/Docker adapter
│ │ │ ├── app.ts # Express application setup
│ │ │ └── index.ts # Server startup and shutdown handling
│ │ └── lambda # AWS Lambda adapter
│ │ └── index.ts # Lambda handler (API Gateway HTTP API v2)
│ ├── core # Shared business logic
│ │ ├── env.ts # Environment variable handler
│ │ ├── logger.ts # Application logging configuration
│ │ ├── middlewares # Middlewares directory
│ │ └── routes # API routes directory

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Keep the documented source layout in sync with the repo conventions.

This tree now tells contributors to put app/server code under src/adapters/express/ and shared modules under src/core/, but the current project conventions still point to src/app.ts, src/server.ts, src/env.ts, src/logger.ts, and src/routes/. Please resolve that mismatch in this PR so the repo has a single authoritative layout.

Based on learnings: "Applies to src/app.ts : Main Express application setup with middleware chain and route registration must be in src/app.ts", "Applies to src/routes/**/*.{ts,tsx} : All routes must be located in src/routes/ and export an Express Router", "Applies to src/logger.ts : Logger setup with development/production configurations must be in src/logger.ts", "Applies to src/server.ts : HTTP server startup with graceful shutdown handling must be in src/server.ts", and "Applies to src/env.ts : Configure environment variables in src/env.ts including ENABLE_SHUTDOWN, LOG_LEVEL, MAX_DELAY, NODE_ENV, ORIGIN, PORT, KEEP_ALIVE_TIMEOUT, HEADERS_TIMEOUT, and REQUEST_TIMEOUT".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/DEVELOPMENT_GUIDE.md` around lines 9 - 19, The docs tree in
DEVELOPMENT_GUIDE.md is inconsistent with the repository convention; update the
PR so the authoritative layout matches the existing codebase by reverting or
editing the docs to document the canonical files: ensure application setup lives
in src/app.ts (Express app with middleware and route registration), routes live
under src/routes/* exporting Express Router, logger configuration is described
in src/logger.ts (dev/prod configs), server startup and graceful shutdown are
documented for src/server.ts, and environment variables (ENABLE_SHUTDOWN,
LOG_LEVEL, MAX_DELAY, NODE_ENV, ORIGIN, PORT, KEEP_ALIVE_TIMEOUT,
HEADERS_TIMEOUT, REQUEST_TIMEOUT) are described in src/env.ts; reference these
filenames (src/app.ts, src/server.ts, src/env.ts, src/logger.ts, src/routes)
when making the doc changes so the guide and repo conventions are in sync.

Comment thread package.json
"scripts": {
"build": "tsc",
"build:lambda:function": "yarn build && cp package.json dist/ && zip -r lambda-function.zip dist/",
"build:lambda:layer": "mkdir -p lambda-layer/nodejs && cp -r node_modules lambda-layer/nodejs && cd lambda-layer && zip -r ../lambda-layer.zip . && cd .. && rm -rf lambda-layer",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Package only runtime dependencies in the Lambda layer.

Copying the repo’s whole node_modules directory will pull Jest, ESLint, TypeScript, Newman, and the rest of the dev toolchain into the layer. That makes the artifact much larger than necessary and can turn deployment into a size problem. Build the layer from production dependencies only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 12, The current "build:lambda:layer" npm script copies
the entire node_modules (including devDependencies) into the Lambda layer;
change it to install only production dependencies into the layer directory
instead. Update the script so it creates lambda-layer/nodejs and runs a
production install into that folder (for example using npm ci --only=production
--prefix lambda-layer/nodejs or npm install --production --prefix
lambda-layer/nodejs), then zip and clean up as before; ensure you remove the
plain cp -r node_modules step in the "build:lambda:layer" script so
devDependencies like Jest/ESLint/TypeScript are not included.

Comment on lines +26 to +37
const listeningPromise: Promise<number> = new Promise((resolve, reject) => {
const server = createServer(app);
server.listen(0, '127.0.0.1', () => {
const address = server.address();
if (address === null || typeof address === 'string') {
reject(new Error('Unexpected server address format'));
return;
}
resolve(address.port);
});
server.on('error', reject);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing RUNTIME environment variable initialization.

The /error/network route in src/core/routes/error.ts (lines 11-18) checks process.env['RUNTIME'] === 'lambda' to safely return a 400 response instead of calling req.socket.destroy(). However, this Lambda adapter never sets RUNTIME='lambda', so the socket destruction path will execute in Lambda invocations, which won't behave correctly since the socket lifecycle is managed by API Gateway.

🐛 Proposed fix to set RUNTIME before starting the server
+// Set runtime environment for Lambda-specific behavior in routes
+process.env['RUNTIME'] = 'lambda';
+
 /**
  * Starts the Express app on a random loopback port and resolves with the port number.
  * Called once on cold start; subsequent invocations reuse the cached promise.
  */
 const listeningPromise: Promise<number> = new Promise((resolve, reject) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapters/lambda/index.ts` around lines 26 - 37, The Lambda adapter never
sets the RUNTIME env var so requests hit the socket-destroy path; before
creating or starting the HTTP server (i.e., before the listeningPromise /
createServer(app) / server.listen call) set process.env['RUNTIME'] = 'lambda' so
the error route in src/core/routes/error.ts sees RUNTIME==='lambda' and returns
the safe 400 response instead of attempting req.socket.destroy(); ensure this
assignment runs synchronously prior to any server creation/listen logic.

Comment on lines +31 to 44
it('should return 500 for "network" type in lambda runtime', async () => {
process.env['RUNTIME'] = 'lambda';
try {
const response = await request(app)[method]('/error/network');
expect(response.status).toBe(500);
if (method !== 'head') {
expect(response.body).toEqual({
error: { message: 'Network error simulation is not supported in this environment.' },
});
}
} finally {
delete process.env['RUNTIME'];
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the error route implementation
fd -e ts -e tsx -path '*/routes/error*' | head -20

Repository: ryo8000/http-playground-server

Length of output: 242


🏁 Script executed:

# Search for the /error/network route handler and RUNTIME handling
rg -n "network" --type ts -g '**/routes/**' -A 10

Repository: ryo8000/http-playground-server

Length of output: 2663


🏁 Script executed:

# Also check the test file to see current state
cat -n tests/ut/routes/error.test.ts | head -60

Repository: ryo8000/http-playground-server

Length of output: 2743


Update test assertion to match implementation: expect 400 status code, not 500.

The /error/network route returns HTTP 400 (not 500) when RUNTIME is 'lambda'. The test description on line 31 and the assertion on line 35 need correction to match the implementation.

🐛 Proposed fix to match implementation behavior
-    it('should return 500 for "network" type in lambda runtime', async () => {
+    it('should return 400 for "network" type in lambda runtime', async () => {
       process.env['RUNTIME'] = 'lambda';
       try {
         const response = await request(app)[method]('/error/network');
-        expect(response.status).toBe(500);
+        expect(response.status).toBe(400);
         if (method !== 'head') {
           expect(response.body).toEqual({
             error: { message: 'Network error simulation is not supported in this environment.' },
           });
         }
       } finally {
         delete process.env['RUNTIME'];
       }
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should return 500 for "network" type in lambda runtime', async () => {
process.env['RUNTIME'] = 'lambda';
try {
const response = await request(app)[method]('/error/network');
expect(response.status).toBe(500);
if (method !== 'head') {
expect(response.body).toEqual({
error: { message: 'Network error simulation is not supported in this environment.' },
});
}
} finally {
delete process.env['RUNTIME'];
}
});
it('should return 400 for "network" type in lambda runtime', async () => {
process.env['RUNTIME'] = 'lambda';
try {
const response = await request(app)[method]('/error/network');
expect(response.status).toBe(400);
if (method !== 'head') {
expect(response.body).toEqual({
error: { message: 'Network error simulation is not supported in this environment.' },
});
}
} finally {
delete process.env['RUNTIME'];
}
});
🧰 Tools
🪛 GitHub Actions: CI

[error] 35-35: Test failure: errorRouter 'network' type should return 500 but received 400. Command: 'yarn test'. Investigate error handling for /error/network route (GET/POST/PUT/DELETE/etc.).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/ut/routes/error.test.ts` around lines 31 - 44, The test's assertion and
description are wrong for the lambda runtime case: update the it(...)
description and the expect(...) assertion in the test for the '/error/network'
route (inside the test case that sets process.env['RUNTIME']='lambda') to expect
a 400 status code instead of 500; leave the conditional body check for non-head
methods unchanged (it already asserts the error message), and ensure the finally
block still deletes process.env['RUNTIME'] so cleanup remains intact.

@ryo8000 ryo8000 added the enhancement New feature or request label Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant