Skip to content

[Bug]: Express Dependency Issues in A2A-JS Library #73

@ajaynagotha

Description

@ajaynagotha

What happened?

Express Dependency Issues in A2A-JS Library

Problem Statement

The current implementation of the a2a-js library has two significant issues related to Express.js dependency that prevent its usage in edge computing environments like Cloudflare Workers and Vercel Edge Functions.

Issues

1. 🔴 [MAJOR/BLOCKER] Hard Express Dependency in Core Exports

Current Issue:

  • The main server export (src/server/index.ts) includes A2AExpressApp which has a hard import of Express.js
  • This means Express is imported even when consumers don't intend to use the Express wrapper
  • Line 22 in src/server/index.ts: export { A2AExpressApp } from "./a2a_express_app.js";
  • Line 1 in src/server/a2a_express_app.ts: import express, { Request, Response, Express, RequestHandler, ErrorRequestHandler } from 'express';

Impact:

  • Breaks compatibility with edge runtime environments (Cloudflare Workers, Vercel Edge Functions, Deno Deploy)
  • Prevents usage in V8-based runtimes that don't support Node.js core APIs
  • Forces Express dependency even for users who only need the core A2A protocol functionality

2. 🟡 [MINOR] Express as Direct Dependency Instead of Peer Dependency

Current Issue:

  • Express is listed as a direct dependency in package.json
  • Should be either a peerDependency (if intended for existing Express projects) or devDependency (if only for testing/examples)

Current Package.json Dependencies

"dependencies": {
  "@types/cors": "^2.8.17",
  "@types/express": "^4.17.23",
  "body-parser": "^2.2.0",
  "cors": "^2.8.5",
  "express": "^4.21.2",  // ← This should not be a direct dependency
  "uuid": "^11.1.0"
}

Proposed Solutions

Option 1: Move Express Wrapper to Examples Directory (Recommended)

Approach:
Move A2AExpressApp completely out of the main library exports and into examples/documentation.

Implementation:

src/
├── server/
│   ├── index.ts          // ← Remove A2AExpressApp export entirely
│   └── ...               // ← Keep core A2A functionality only
└── examples/
    └── express-integration/
        ├── a2a_express_app.ts
        ├── example-server.ts
        └── README.md

Benefits:

  • Zero runtime errors: No peer dependency issues
  • Edge runtime compatible: Core library has no Express imports
  • Clear separation: Express integration is clearly an example/pattern
  • Framework agnostic: Users can adapt the pattern to any HTTP framework
  • No dependency confusion: Express is only needed if you copy the example

Usage Pattern:

// Core A2A functionality (works everywhere)
import { DefaultRequestHandler, JsonRpcTransportHandler } from '@a2a-js/sdk/server';

// Users copy and adapt the Express example when needed
// No hidden dependencies or runtime surprises

Option 2: Subpath Exports with Runtime Safety (Alternative)

⚠️ Issue with Subpath Exports:
Moving Express to peer dependencies creates runtime errors is express is not installed separately:

# User installs the library
npm install @a2a-js/sdk

# User tries to use Express integration
import { A2AExpressApp } from '@a2a-js/sdk/server/express';
// ❌ Error: Cannot find module 'express' 
// User forgot to install Express separately

If subpath exports are used, they need runtime safety:

// src/server/express/index.ts
let express: any;
let Express: any;

try {
  const expressModule = await import('express');
  express = expressModule.default;
  Express = expressModule.Express;
} catch (error) {
  throw new Error(
    'Express is required to use A2AExpressApp. Install it with: npm install express'
  );
}

export class A2AExpressApp {
  // Implementation using the dynamically imported express
}

But this adds complexity and still has issues:

  • ❌ Runtime errors instead of install-time errors
  • ❌ Dynamic imports complicate TypeScript types
  • ❌ Worse developer experience
  • ❌ Still breaks in edge environments that don't support dynamic imports

Why Option 1 (Examples) is Better

Current Problems with Peer Dependencies:

  1. Silent failures: npm install succeeds, runtime fails
  2. Complex error messages: Users don't understand peer dependency warnings
  3. Version conflicts: Different projects might need different Express versions
  4. Edge runtime issues: Dynamic imports don't work in all environments

Examples Directory Advantages:

  1. Explicit choice: Users consciously decide to use Express integration
  2. No hidden dependencies: What you see is what you get
  3. Adaptable patterns: Users can modify for their framework (Fastify, Koa, etc.)
  4. Clear documentation: Example includes setup instructions
  5. Version freedom: Users choose their Express version

Implementation Plan

Step 1: Remove Express from Core Exports

// src/server/index.ts - Remove this line:
// export { A2AExpressApp } from "./a2a_express_app.js";

Step 2: Move to Examples

examples/
└── express-integration/
    ├── README.md                    // ← Setup and usage instructions
    ├── a2a-express-app.ts          // ← Moved from src/server/
    ├── basic-server.ts             // ← Simple example
    └── advanced-server.ts          // ← With middleware, error handling

Step 3: Update Package.json

{
  "dependencies": {
    "@types/cors": "^2.8.17",
    "cors": "^2.8.5",
    "uuid": "^11.1.0"
    // ← Remove Express and @types/express
  },
  "devDependencies": {
    "express": "^4.21.2",           // ← Only for examples/testing
    "@types/express": "^4.17.23"
  }
}

Step 4: Documentation

Create comprehensive documentation showing:

  • Core A2A usage (framework agnostic)
  • Express integration example
  • Other framework examples (Fastify, Koa, etc.)

Migration Guide for Existing Users

// Before (current)
import { A2AExpressApp } from '@a2a-js/sdk/server';

// After (copy example to your project)
// 1. Copy examples/express-integration/a2a-express-app.ts to your project
// 2. Install Express: npm install express @types/express
// 3. Import from your local copy
import { A2AExpressApp } from './utils/a2a-express-app';

Benefits Summary

Edge Runtime Compatible: Core library works in all environments
No Runtime Surprises: No hidden peer dependencies
Framework Agnostic: Examples for multiple HTTP frameworks
Smaller Bundle: Core library has minimal dependencies
Clear Intent: Users explicitly choose their HTTP framework integration
Better Maintenance: Examples are easier to update than library code
Community Contributions: Easy for community to add framework examples

Related Issues

Priority

High Priority - This blocks adoption in the growing edge computing ecosystem and affects developer experience with unexpected runtime errors.

Relevant log output

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions