I love GraphQL and serverless architectures so it shouldn’t be a surprise that I’m a huge fan of AWS AppSync. It’s quickly become my default starting point for any new application. While AppSync is awesome it does have a few rough edges and one of those is the endpoint hostname.

When you create a new AppSync API you’ll receive an endpoint with a URL that looks like https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.XXX-XXXXXX-X.amazonaws.com/graphql

It’s not particularly attractive but that’s not the issue. The first part of the hostname is randomly generated by AWS when you create a new API. Having our application depend on a randomly generated URL for the API is potential disaster for the business.

You only need to look at serverless forums to find examples of people posting in desperation after someone accidentally deleted their API Gateway or AppSync API because the AWS generated URL was used in their client application and they can’t re-create an API Gateway or AppSync API with the same hostname. While setting DeletionPolicy: Retain in the CloudFormation template reduces the risk I’m still waiting for the day when I receive a phone call from a developer or operations person panicing because they’ve just deleted the production AppSync API and they can’t re-create the old URL.

With web applications this is rarely a major issue because you can deploy an update with the new API endpoint quickly. It’s a different story if you need to compile an Android/iOS application, publish the update in the app store and then wait for devices to update the software. Your users will probably experience an outage that lasts days.

The API Gateway has a solution to this with custom domains and I was really hoping that AppSync would support them by now.

With our latest application about to go into production I decided to experiment with putting an API Gateway in front of AppSync. The API Gateway supports custom domain names and I can configure it to proxy all requests to the AppSync API endpoint.

My first step in testing this was to setup an AppSync API using AWS Amplify CLI. It’s overkill, and I’m not going to use the client, but it’s also much faster than setting up an AppSync API through the AWS console. Once I had my AppSync API I sent it a few queries using RESTClient to confirm it was working correctly.

Next I created an API Gateway using SAM with a custom domain name. The definition for the API Gateway was written using the Open API spec. All incoming requests are proxied to the AppSync API using the API Gateway HTTP proxy integration.

Here’s the template.yml for SAM.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AppSync custom domain template

Parameters:
    DomainName:
        Type: String
    Stage:
        Type: String
        Default: dev

Resources:
    ApiGw:
        Type: AWS::Serverless::Api
        Properties:
            StageName: !Ref Stage
            DefinitionBody:
                openapi: "3.0.0"
                info:
                    version: 1.0.0
                    title: AppSync custom domain
                    description: AppSync custom domain
                    contact:
                        name: Richard Buggy
                        email: rich@goingserverless.com
                        url: http://www.goingserverless.com/
                servers:
                    - url: https://api.goingserverless.com
                paths:
                    /{proxy+}:
                        "x-amazon-apigateway-any-method":
                            summary: "GraphQL Proxy"
                            operationId: graphQL
                            x-amazon-apigateway-integration:
                                uri: https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.XXX-XXXXXX-X.amazonaws.com/{proxy}
                                passthroughBehavior: when_no_match
                                httpMethod: ANY
                                type: http_proxy
            EndpointConfiguration: REGIONAL

    ApiGwCertificate:
        Type: AWS::CertificateManager::Certificate
        Properties: 
            DomainName: !Ref DomainName
            ValidationMethod: DNS

    ApiGwDomainName:
        Type: AWS::ApiGateway::DomainName
        DependsOn:
            - ApiGwCertificate
        Properties: 
            DomainName: !Ref DomainName
            EndpointConfiguration:
                Types:
                    - REGIONAL
            RegionalCertificateArn: !Ref ApiGwCertificate

    ApiGwBasePathMapping:
        Type: AWS::ApiGateway::BasePathMapping
        DependsOn:
            - ApiGwDomainName
            - ApiGw
        Properties:
            DomainName: !Ref DomainName
            RestApiId: !Ref ApiGw
            Stage: !Ref Stage

Once the API Gateway was setup I used RESTClient to check it was proxying correctly to the AppSync API. Success!!

While it introduces extra latency and it isn’t as convenient as adding a custom domain name directly to AppSync it does remove the worry that our endpoint will simply disappear one day due to human error. When AppSync introduces custom domain name support I’ll remove the API Gateway and attach the domain name directly to the AppSync API.