Skip to content

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

Open
@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 ? '[email protected]' : '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

No one assigned

    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 utilityhelp-wantedWe would really appreciate some support from community for this one

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions