Description
Describe the feature
When building real-time applications with aws_appsync.EventApi
, a common requirement is to pass values synthesized during CDK deployment (e.g., a DynamoDB table name) into the JavaScript resolver code that runs within the AppSync runtime.
Following best practices, developers often prefer separating resolver logic into dedicated .js
files using Code.fromAsset
. However, unlike Code.fromInline
, Code.fromAsset
cannot directly interpolate CDK values (like ${myTable.tableName}
). The standard mechanism in AppSync for injecting such dynamic values into the resolver context is via MappingTemplate
(VTL) and ctx.stash
.
Currently, the EventApi
construct's addChannelNamespace
method does not appear to support specifying requestMappingTemplate
or responseMappingTemplate
when configured with:
Code.fromAsset(...)
for the resolver code.- A direct data source integration (e.g.,
AppSyncDynamoDbDataSource
) inpublishHandlerConfig
orsubscribeHandlerConfig
.
This prevents developers from using the clean Code.fromAsset
approach combined with the standard MappingTemplate
mechanism for passing dynamic values, forcing them into less ideal workarounds and creating an inconsistency compared to standard GraphqlApi
resolvers.
Current Cumbersome Workaround: Code.fromInline
To pass the table name currently, one must embed the entire JavaScript resolver logic within a potentially very long template literal string inside the CDK stack code, using TypeScript interpolation. This works but has significant drawbacks:
- Poor Readability/Maintainability: Large blocks of JS code embedded in TS infrastructure code are hard to read and manage.
- No Standard Tooling: Difficult to apply JS linters, formatters, syntax highlighting, or testing frameworks effectively to the embedded code.
- Violates Separation of Concerns: Infrastructure code becomes tightly coupled with application logic.
(The full code example for the Code.fromInline
workaround is omitted here for brevity, but available in the previous comment if needed for context).
Desired Developer Experience: Aligning with GraphqlApi
Patterns
The ideal developer experience would align with how resolvers are configured for the standard aws_appsync.GraphqlApi
. Developers should be able to:
- Write the JS resolver logic in a separate
my-resolver.js
file. - Use
Code.fromAsset
in the CDK stack to reference this file. - Utilize the existing
MappingTemplate
class (docs) withinaddChannelNamespace
to define request/response templates, enabling values (liketableName
) to be injected intoctx.stash
. - Access these values within
my-resolver.js
viactx.stash.tableName
.
Illustrative Code for Desired DX (If Supported):
This code shows how developers would like to configure EventApi
's channel namespace, mirroring patterns available elsewhere in aws_appsync
.
import { Stack, StackProps, App, RemovalPolicy } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import {
EventApi,
Code,
MappingTemplate, // CDK provides this utility class
ChannelNamespaceOptions, // Interface would need updating
HandlerConfig, // Interface might need updating conditionally
} from 'aws-cdk-lib/aws-appsync';
import { Table, AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb';
import { join } from 'path';
import * as fs from 'fs';
import * as path from 'path';
// --- Placeholder src/resolvers/my-asset-resolver.js ---
/*
import { util } from '@aws-appsync/utils';
import * as ddb from '@aws-appsync/utils/dynamodb';
// Ideal: Get table name from stash
const getTable = (ctx) => {
if (!ctx.stash || !ctx.stash.tableName) {
util.error("Table name not found in stash. Mapping template likely missing/incorrect.");
}
return ctx.stash.tableName;
};
export const onPublish = {
request(ctx) {
const TABLE = getTable(ctx); // Use stashed value
const channel = ctx.info.channel.path;
const timestamp = util.time.nowISO8601();
console.log(\`Publishing to table: \${TABLE}, channel: \${channel}\`);
// ... DDB BatchPut logic ...
return ddb.batchPut({ tables: { [TABLE]: [ /* ... items ... * / ] } });
},
response(ctx) {
// ... response logic ...
return [];
}
}
export const onSubscribe = {
request(ctx) {
const TABLE = getTable(ctx); // Use stashed value
// ... request logic ...
// ... DDB Put logic ...
return ddb.put({ table: TABLE, item: { /* ... item ... * / } });
},
response(ctx) {
// ... response logic ...
return null;
}
}
*/
// --- End of placeholder JS ---
// Ensure placeholder directory and file exist for Code.fromAsset
const resolverDir = path.join(__dirname, '../src/resolvers');
const resolverFile = path.join(resolverDir, 'my-asset-resolver.js'); // Different name for clarity
if (!fs.existsSync(resolverDir)) {
fs.mkdirSync(resolverDir, { recursive: true });
}
if (!fs.existsSync(resolverFile)) {
fs.writeFileSync(resolverFile, '// Placeholder content for asset resolver');
}
export class EventApiDesiredDxStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const eventApi = new EventApi(this, 'MyEventApiDesired', {
apiName: 'my-event-api-desired',
});
const myTable = new Table(this, 'MyDesiredTable', {
partitionKey: { name: 'pk', type: AttributeType.STRING },
billingMode: BillingMode.PAY_PER_REQUEST,
removalPolicy: RemovalPolicy.DESTROY,
});
const ddbDataSource = eventApi.addDynamoDbDataSource('MyDdbDsDesired', myTable);
// IDEAL DEVELOPER EXPERIENCE:
eventApi.addChannelNamespace('asset-namespace', {
// Use separate file - clean!
code: Code.fromAsset(resolverFile),
// Define mapping templates directly (similar to GraphqlApi resolvers)
requestMappingTemplate: MappingTemplate.fromString(
// Simple template to pass table name
\`#set(\$ctx.stash.tableName = "\${myTable.tableName}")\\n{}\`
),
// Response template might also be needed depending on direct source interaction
responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
// Handler configs likely just need the data source
publishHandlerConfig: {
dataSource: ddbDataSource,
},
subscribeHandlerConfig: {
dataSource: ddbDataSource,
},
});
}
}
const app = new App();
new EventApiDesiredDxStack(app, 'EventApiDesiredDxStack');
Use Case
See above.
Proposed Solution
No response
Other Information
No response
Acknowledgements
- I may be able to implement this feature request
- This feature might incur a breaking change
AWS CDK Library version (aws-cdk-lib)
2.194.0
AWS CDK CLI version
2.1013.0
Environment details (OS name and version, etc.)
Ubuntu 24.04