Skip to content

Feature request: Add includeRouter and context sharing support to AppSync GraphQL resolver #4131

@dreamorosi

Description

@dreamorosi

Use case

The TypeScript version of Powertools already has a Router class for organizing GraphQL resolvers, but it's missing a functionality that exists in the Python version:

  1. Router Composition: The ability to include multiple Router instances in the main AppSyncGraphQLResolver using an includeRouter() method
  2. Context Sharing: The ability to share contextual data between the main resolver and included routers using appendContext()

Currently, developers can create Router instances but cannot easily compose them into a main resolver, limiting the modularity benefits. The Python version already has this functionality through include_router() and append_context() methods.

Solution/User Experience

Add the missing router composition and context sharing functionality to match the Python implementation:

  1. includeRouter() method: Allow AppSyncGraphQLResolver to include multiple Router instances
  2. appendContext() method: Enable sharing data between main resolver and routers
  3. Context access: Allow routers to access shared context data in their handlers

Example Usage:

// userRoutes.ts
import { Router } from '@aws-lambda-powertools/event-handler/appsync-graphql';

const userRouter = new Router();

userRouter.onQuery<{ id: string }>('getUser', async ({ id }, { context }) => {
  // Access shared context
  const isAdmin = context?.get('isAdmin', false);
  
  return { 
    id, 
    name: 'John Doe', 
    email: isAdmin ? 'john@example.com' : 'hidden' 
  };
});

userRouter.onMutation<{ name: string; email: string }>('createUser', async ({ name, email }) => {
  return { id: makeId(), name, email };
});

export { userRouter };
// todoRoutes.ts
import { Router } from '@aws-lambda-powertools/event-handler/appsync-graphql';

const todoRouter = new Router();

todoRouter.onQuery<{ id: string }>('getTodo', async ({ id }, { context }) => {
  const requestId = context?.get('requestId');
  logger.info('Fetching todo', { id, requestId });
  
  return { id, title: 'Sample Todo', completed: false };
});

export { todoRouter };
// index.ts - Main Lambda handler
import { AppSyncGraphQLResolver } from '@aws-lambda-powertools/event-handler/appsync-graphql';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';
import { userRouter } from './userRoutes.js';
import { todoRouter } from './todoRoutes.js';

const logger = new Logger({ serviceName: 'GraphQLAPI' });
const app = new AppSyncGraphQLResolver({ logger });

// Include routers - NEW FUNCTIONALITY
app.includeRouter(userRouter);
app.includeRouter(todoRouter);

export const handler = async (event: unknown, context: Context) => {
  // Share context between main app and routers - NEW FUNCTIONALITY
  app.appendContext({ 
    isAdmin: true, 
    requestId: context.awsRequestId,
    timestamp: Date.now()
  });
  
  return app.resolve(event, context);
};

Implementation Details:

  1. Router Registry Merging: The includeRouter() method should merge the router's resolver registry with the main resolver's registry
  2. Context Management:
    • Add a context storage mechanism (Map or similar) to store key-value pairs
    • Context should be accessible in resolver handlers via the options parameter
    • Context should be cleared after each invocation for safety
  3. Type Safety: Ensure proper TypeScript types for context methods and router inclusion

Expected API:

class AppSyncGraphQLResolver extends Router {
  // NEW: Include a router instance
  public includeRouter(router: Router): void;
  
  // NEW: Add context data to be shared with routers
  public appendContext(data: Record<string, unknown>): void;
  
  // NEW: Access to context (internal)
  public readonly context: Map<string, unknown>;
}

// Updated resolver handler signature to include context access
type ResolverHandler<TParams = Record<string, unknown>> = (
  args: TParams,
  options: {
    event: AppSyncResolverEvent<TParams>;
    context: Context;
    // NEW: Shared context access
    context?: Map<string, unknown>;
  }
) => Promise<unknown> | unknown;

Alternative solutions

  1. Manual Registry Merging: Developers could manually access the router's registry and merge it, but this exposes internal implementation details
  2. Inheritance Pattern: Create a base class that both Router and AppSyncGraphQLResolver extend, but this changes the current architecture
  3. Composition via Constructor: Pass routers during AppSyncGraphQLResolver construction, but this is less flexible than the include pattern

Benefits:

  • Consistency: Matches the Python implementation's API
  • Modularity: Enables better code organization across multiple files/modules
  • Context Sharing: Allows passing request-scoped data between resolvers
  • Backward Compatibility: Doesn't break existing Router or AppSyncGraphQLResolver usage

Acknowledgment

Future readers

Please react with 👍 and your use case to help us understand customer demand.

Metadata

Metadata

Assignees

Labels

confirmedThe scope is clear, ready for implementationevent-handlerThis item relates to the Event Handler Utilityfeature-requestThis item refers to a feature request for an existing or new utility

Type

No type

Projects

Status

Working on it

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions