Generate AppSync VTL files without Amplify CLI
By: Ronny Roeller
Tags: GraphQL, Cloud Computing, Serverless, AWS, Serverless Architecture

Generate AppSync VTL files without Amplify CLI

Amplify CLI comes with an excellent generator for VTL files — but also a ton of other stuff. Learn how to leverage Amplify’s VTL file generator when using another framework like serverless. Ronny Roeller Follow Dec 10, 2020 · 2 min read

AppSync is AWS’ hosted version of the Apollo GraphQL server. There are many amazing things about AppSync but there is one thing widely disliked in the community: the need to write resolvers in VTL files.

Lambda resolvers for AppSync are one option to get out of VTL but, for performance/cost reasons, one tends to get stuck with VTL sooner or later. Amplify CLI offers another solution: developers simply annotate the GraphQL schema with directives, and Amplify CLI automagically provisions AppSync with the correct VTL files. A very smooth developer experience!

Yet, Ops tends to be less excited about Amplify CLI precisely because it’s too “automagically”. They want more control of what’s happening, how things are named, and the like.

That’s pretty much where we found ourselves: We had opted for serverless to provision our AWS stacks but were far from excited about writing & maintaining VTL files for AppSync.

Could we somehow use the excellent VTL generator without committing to the complete Amplify CLI?

Diving into the Amplify CLI codebase showed that it’s way less monolithic than it appeared initially. In fact, the GraphQL transformation logic is neatly encapsulated in separated modules, which can be easily used without the Amplify CLI. And that’s what we did.

First, we use it to create the full GraphQL schema from the annotated schema:

const { ModelAuthTransformer } = require('graphql-auth-transformer');

const { ModelConnectionTransformer } = require('graphql-connection-transformer');

const { DynamoDBModelTransformer } = require('graphql-dynamodb-transformer');

const { GraphQLTransform } = require('graphql-transformer-core'); const SCHEMA = fs.readFileSync('schema.src.graphql')).toString(); const transformer = new GraphQLTransform({

transformers: [

new DynamoDBModelTransformer(),

new ModelConnectionTransformer(),

new ModelAuthTransformer({

authConfig: {

defaultAuthentication: {

authenticationType: 'AMAZON_COGNITO_USER_POOLS',

},

},

}),

],

});

const out = transformer.transform(SCHEMA);

fs.writeFileSync('./schema.graphql', out.schema);

Then, we extract the VTL files from the transformer output:

Object.keys(out.resolvers).forEach(key => {

const vtl = out.resolvers[key];

fs.writeFileSync(path.resolve('./mapping-templates', key), vtl);

});

Finally, we create the mappingTemplates configuration for the serverless-appsync-plugin:

function getDataSource(dataSourceName) {

if (dataSourceName === 'NONE') {

return 'NONE';

}

if (dataSourceName.name === 'Fn::ImportValue') {

return dataSourceName.payload.payload[1][2];

}

return dataSourceName['Fn::GetAtt'][0];

} function createServerlessResources(entity) {

return Object.values(out.stacks[entity].Resources)

.filter(resource => resource.Type === 'AWS::AppSync::Resolver')

.map(resource => {

const props = resource.Properties;

const type = props.TypeName;

const field = props.FieldName;

const datasource = getDataSource(props.DataSourceName); return `

- type: ${type}

field: ${field}

dataSource: ${datasource}

request: ${type}.${field}.req.vtl

response: ${type}.${field}.res.vtl`;

})

.join('');

} const serverless = Object.keys(out.stacks)

.map(createServerlessResources)

.join('

');

fs.writeFileSync('./serverless.mapping-templates.yml', serverless);

As a last step, we reference the auto-generated files from our serverless.yml :

custom:

appSync:

name: myapp

schema: schema.graphql

mappingTemplateLocation: mapping-location

mappingTemplates:

- ${file(serverless.mapping-templates.yml)}

Happy coding!