Open austinamorusoyardstick opened 1 year ago
bump
Need this soon please!
Third'd - this would be a great feature to have!
Would be much appreciated by developers
We would appreciate this too!
bump
bump!
bump
bump!!
➕ 1️⃣
bump
Honestly changing all the vlt templates to JS would likely enable to Amplify community to issue patches easier and fixes / plugins for issues surrounding resolvers.
Any plans for this rebuild of the resolvers?
bump
bump
Seems like AWS phases out of VTL if favor of APPSYNC_JS (source: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference.html),
are there any updates on this feature?
bump
its a nightmare to do graphql with appsync.. i update to node 16, update my sls to 3, update appsync plugin to support js resolver and, what, the simulator broke everything
Hi - while we update our documentation to illustrate how to do this with the Amplify CLI, I do want to bring attention to the fact that the GraphQL API category is now available first-class as a CDK construct. In the CDK construct, you can use JavaScript resolvers. Check out our announcement post here: https://aws.amazon.com/blogs/mobile/announcing-aws-amplifys-graphql-api-cdk-construct-deploy-real-time-graphql-api-and-data-stack-on-aws/
Yes please
This would be lovely, yes please!
bump
bump
BUMP
bump 🤭
@renebrandel - you mentioned there'd be updates to the documentation, but in the mean time, can we get even a rough overview/walkthrough of how to do it "by hand" for existing amplify projects that are cli-dependent/cli-generated?
Please? :)
I took a look here: https://docs.aws.amazon.com/appsync/latest/devguide/configuring-resolvers-js.html
And then I got impatient, and thought maybe I'd give this a try. I kinda took a sledgehammer to it, so please forgive me if there are more elegant ways to achieve this.
In case anybody is as masochistic as I am, I think I got pretty close with this.
I don't think it works yet (it's 4:00 AM here and I need to sleep), but it's a close-enough starting point, in case anyone wants to hack on this together.
👉 If anyone does bother to try this, and is successful (or finds ways to make it work), please share back your solution 😎.
amplify add custom --> name: MyCustomResolvers
cdk-stack.ts
import type { Construct } from 'constructs'
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'
import * as cdk from 'aws-cdk-lib'
import * as appsync from 'aws-cdk-lib/aws-appsync'
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'
import type { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'
export class cdkStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props?: cdk.StackProps,
amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps,
) {
super(scope, id, props)
/* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
const _env = new cdk.CfnParameter(this, 'env', {
type: 'String',
description: 'Current Amplify CLI env name',
})
// Get the project name from AmplifyHelper
// and create a standard prefix for all resources - "projectName-env"
const { projectName, envName } = AmplifyHelpers.getProjectInfo()
const _resourcePrefix = `${projectName}-${envName}`
/*
* 🛑 📢
* There is a known bug (https://github.com/aws-amplify/amplify-cli/issues/13532) that *may* prevent
* the use of the addResourceDependency function from AmplifyHelpers.
*
* If you run into that bug, you can use the following workaround to reference dependent resources in your CDK stack.
*
* The formula is: category + resourceName + outputName
*
* For a GraphQL API example: apiMyApiNameGraphQLAPIIdOutput
* For an S3 Storage example: storageMyProjectStorageStorageBucketName
*
* Where:
* category: api
* resourceName: <YOUR_API_NAME>
* outputName: GraphQLAPIIdOutput
*
* const resourceCategory = 'api'
* const resourceName = 'myprojectV2AwesomeAPI'
* const outputName = 'GraphQLAPIIdOutput'
* const GraphQLAPIIdOutput = resourceCategory + resourceName + outputName
*
*/
const apiResourceReference: AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(
this,
amplifyResourceProps.category,
amplifyResourceProps.resourceName,
[
{
category: 'api',
resourceName: 'myprojectV2AwesomeAPI',
},
],
)
// Instantiate a reference to the API using the API ID from the Amplify-generated stack
const api = appsync.GraphqlApi.fromGraphqlApiAttributes(this, 'api', {
graphqlApiId: apiResourceReference.api.myprojectV2AwesomeAPI.GraphQLAPIIdOutput,
})
// Time to get our CloudFormation on - there's no escaping it!
const graphqlApiId = apiResourceReference.api.myprojectV2AwesomeAPI.GraphQLAPIIdOutput
const tableName = 'UserTable'
const importString = `\${${graphqlApiId}}:GetAtt:${tableName}:Name`
/* Get the name of the table using cloudformation stack outputs
* We are attempting to mimic this:
*
* "Fn::ImportValue": {
* "Fn::Sub": "${apimyprojectV2AwesomeAPIGraphQLAPIIdOutput}:GetAtt:UserTable:Name"
* }
*/
// Get the name of the table using cloudformation stack outputs - e.g. User-5mt2wm3hzrgptjlq7qcbryxcgi-mybackend
const UserTableName = cdk.Fn.importValue(cdk.Fn.sub(importString))
// Instantiate a reference to the DynamoDB table using the table name from the Amplify-generated stack
const userTableDDB = dynamodb.Table.fromTableName(this, 'table', UserTableName)
// Create a new AppSync function and pass in the API and the DynamoDB table objects
const appsyncFuncScanUsersTable = new appsync.AppsyncFunction(this, 'func-scan-users', {
name: 'scan_users_func_1',
api,
dataSource: api.addDynamoDbDataSource('table-for-posts', userTableDDB),
code: appsync.Code.fromInline(`
export function request(ctx) {
return { operation: 'Scan' };
}
export function response(ctx) {
return ctx.result.items;
}
`),
runtime: appsync.FunctionRuntime.JS_1_0_0,
})
// Create a new AppSync resolver
const _resolver = new appsync.Resolver(this, 'custom-resolver', {
api,
typeName: 'Query',
fieldName: 'getPost',
code: appsync.Code.fromInline(`
export function request(ctx) {
return {};
}
export function response(ctx) {
return ctx.prev.result;
}
`),
runtime: appsync.FunctionRuntime.JS_1_0_0,
pipelineConfig: [appsyncFuncScanUsersTable],
})
}
}
package.json
{
"name": "custom-resource",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@aws-amplify/cli-extensibility-helper": "^3.0.25",
"aws-cdk-lib": "~2.80.0",
"constructs": "^10.3.0"
},
"devDependencies": {
"typescript": "^5.4.2"
},
"resolutions": {
"aws-cdk-lib": "~2.80.0"
}
}
^^ This gets close, but it won't work. There's 16 hours of my life I won't get back. Maybe someone can figure out what I'm doing wrong by trying it themselves. I'm good and stuck.
Abandon all hope, ye who enter here. Waiting for any help or instruction from @renebrandel & our Amplify friends... 🫠
Hi - @armenr - sorry for the dropping the ball here. So here's a working sample that should work for you.
For the JS resolvers, I build a quick "echo this message" resolver example. The graphQL schema looks like this:
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!
type Todo @model {
id: ID!
name: String!
description: String
}
type Query {
echo(message: String): String # your custom queries here
}
The custom stack looks like this:
import * as cdk from 'aws-cdk-lib';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
import * as appsync from 'aws-cdk-lib/aws-appsync';
import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';
import { Construct } from 'constructs';
const jsResolverTemplate = `
export function request(ctx) {
return {
payload: null
}
}
export function response(ctx) {
return ctx.arguments.message
}
`
export class cdkStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props?: cdk.StackProps,
amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps
) {
super(scope, id, props);
/* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
new cdk.CfnParameter(this, 'env', {
type: 'String',
description: 'Current Amplify CLI env name'
});
// Access other Amplify Resources
const retVal: AmplifyDependentResourcesAttributes =
AmplifyHelpers.addResourceDependency(
this,
amplifyResourceProps.category,
amplifyResourceProps.resourceName,
[
{
category: 'api',
resourceName: 'gen1jsresolver'
}
]
);
const resolver = new appsync.CfnResolver(this, 'CustomResolver', {
// apiId: retVal.api.new.GraphQLAPIIdOutput,
// https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887
// If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack.
// Previously the ApiId is the variable Name which is wrong , it should be variable value as below
apiId: cdk.Fn.ref(retVal.api.gen1jsresolver.GraphQLAPIIdOutput),
fieldName: 'echo',
typeName: 'Query', // Query | Mutation | Subscription
code: jsResolverTemplate,
dataSourceName: 'NONE_DS', // DataSource name
runtime: {
name: 'APPSYNC_JS',
runtimeVersion: '1.0.0'
}
});
}
}
Do you mind sharing more about the exact error you're facing? My guess is that this line is probably where the error is coming from:
graphqlApiId: apiResourceReference.api.myprojectV2AwesomeAPI.GraphQLAPIIdOutput,
My guess is that the resolution of the reference doesn't happen correctly, thus the API won't be referenced. It should be this:
graphqlApiId: cdk.Fn.ref(apiResourceReference.api.myprojectV2AwesomeAPI.GraphQLAPIIdOutput),
@armenr - also cut a docs PR https://github.com/aws-amplify/docs/pull/7087
Dude! @renebrandel - this is fantastic. Thanks for the quick reply and the Customer Obsession!
Thank you 😎 I'm going to try it out and post back.
I'm sure it's going to work.
@renebrandel - This was great. Seems to work just fine! 👍🏼
One last question, just to be sure. Let's say I run the following command:
aws appsync list-data-sources --api-id <MY_API_ID> --no-cli-pager | grep name
And it provides the following output:
... # other stuff
name: UserTable
This means, that if I wanted the resolver to be hooked up to the User
model/table, then I can replace dataSourceName: 'NONE_DS', // DataSource name
with dataSourceName: 'UserTable'
, right?
Would I require any further changes or would that be it? Thanks again, a lot!
That's correct. We're making this experience significantly more seamless in Gen 2. That should help in the future. Gen 2 is still in dev preview. https://docs.amplify.aws/gen2/build-a-backend/data/custom-business-logic/
@renebrandel - Heck yes. I excitedly check in on the gen2 dashboard + the docs every week...and am very excitedly waiting for it to go GA and out of Preview.
I guess since I've got your attention...one more question (sorry if it doesn't belong on this Issue thread):
Will we be able to export/eject/migrate our gen1 project into gen2 when gen2 is ready? We're making full use of everything in gen1...
Hi - let's open a separate issue on the amplify-backend repo to discuss Gen 2 issues if this response isn't sufficient for now. Just to keep this thread focused around JS resolvers.
We're working on the migration strategy for Gen 1 to Gen 2. At the time of Gen 2 GA, you'll be able to rebuild most use cases with Gen 2 and build out significantly more (for example, JS resolvers). We're building up detailed feature matrix, so customers will understand which use case is supported in:
After Gen 2 GA, our highest priority item is building a migration path for auth, data, and storage. Specifically, our plan is to migrate the stateful resources from your Gen 1 project to Gen 2. I.e. porting over your user pool, database tables, buckets. For "stateless" services (Lambda functions, AppSync API), we'll provide a guide on how to recreate in functional parity in Gen 2.
Thank you both for the input on this thread, I have just caught up with the issue after my holiday.
We are currently also working on getting the JS resolvers up and running and were also able to get this working for queries and mutations that we manually added to the schema. However, we would like to replace the amplify generated VTL resolvers that are made during the build. Do you have any guidance on how to do this? I created an issue for this some time ago but haven't received any replies on that: #2211
@renebrandel - no problem, I didn't mean to veer us of course. I'm super grateful for the help and clarification here.
Just a few more specific questions about this implementation, and then I'll buzz off:
Is there a specific reason to use const resolver = new appsync.CfnResolver
VS const _resolver = new appsync.Resolver(
- or stated differently - is there a specific reason not to use VS the other?
If there is, in fact, a constraint which dictates we have to use that, is there any reason why you would recommend against us doing something like this?
// Read the resolver code from a file
const resolverCodePath = path.join(__dirname, 'resolverCode.js');
const jsResolverTemplate = readFileSync(resolverCodePath, 'utf8');
const resolver = new appsync.CfnResolver(this, 'CustomResolver', {
apiId: cdk.Fn.ref(retVal.api.gen1jsresolver.GraphQLAPIIdOutput),
fieldName: 'echo',
typeName: 'Query', // Query | Mutation | Subscription
code: jsResolverTemplate,
dataSourceName: 'NONE_DS', // DataSource name
runtime: {
name: 'APPSYNC_JS',
runtimeVersion: '1.0.0'
}
});
Customer reason/use-case: For us, the idea would be to write the resolvers in TypeScript, and then have a pre-push hook take care of transpiling the resolvers before the amplify push
happens. For that reason, we'd want to be able to pass the code in as a file (or read in the file and pass it to the resolver constructor).
I'm also interested in this use case. Typescript is integral to how we work with JS code for Lambda, etc.
@naedx - I think I've found a useful pattern for that. I'll post back with our approach shortly.
@armenr - sorry for the late reply. The core difference is that the CfnResolver
is a "L1 construct" vs. Resolver
is an "L2 construct". Using the L1 construct in Gen 1 is simpler for referencing the API but you're not prohibited to use the L2 construct. Either case though the TypeScript compilation workflow would likely remain the same using your suggestion of leveraging a prePush
command hook.
Is this feature request related to a new or existing Amplify category?
No response
Is this related to another service?
Appsync JavaScript resolvers
Describe the feature you'd like to request
New feature in appsync how to use within amplify?
https://aws.amazon.com/blogs/aws/aws-appsync-graphql-apis-supports-javascript-resolvers/
Describe the solution you'd like
Steps to use within amplify
Describe alternatives you've considered
Manual work around that doesn't change the way we deploy
Additional context
No response
Is this something that you'd be interested in working on?
Would this feature include a breaking change?