• Explore the unified API Platform
        • BUILD APIs
        • Kong Insomnia
        • API Design
        • API Mocking
        • API Testing & Debugging
        • MCP Client
        • RUN APIs
        • API Gateway
        • Context Mesh
        • AI Gateway
        • Event Gateway
        • Kubernetes Operator
        • Service Mesh
        • Ingress Controller
        • Runtime Management
        • DISCOVER APIs
        • Developer Portal
        • Service Catalog
        • MCP Registry
        • GOVERN APIs
        • Metering & Billing
        • APIOps & Automation
        • API Observability
        • Why Kong?
      • CLOUD
      • Cloud API Gateways
      • Need a self-hosted or hybrid option?
      • COMPARE
      • Considering AI Gateway alternatives?
      • Kong vs. Postman
      • Kong vs. MuleSoft
      • Kong vs. Apigee
      • Kong vs. IBM
      • GET STARTED
      • Sign Up for Kong Konnect
      • Documentation
  • Agents
      • FOR PLATFORM TEAMS
      • Developer Platform
      • Kubernetes & Microservices
      • Observability
      • Service Mesh Connectivity
      • Kafka Event Streaming
      • FOR EXECUTIVES
      • AI Connectivity
      • Open Banking
      • Legacy Migration
      • Platform Cost Reduction
      • Kafka Cost Optimization
      • API Monetization
      • AI Monetization
      • AI FinOps
      • FOR AI TEAMS
      • AI Cost Control
      • AI Governance
      • AI Integration
      • AI Security
      • Agentic Infrastructure
      • MCP Production
      • MCP Traffic Gateway
      • FOR DEVELOPERS
      • Mobile App API Development
      • GenAI App Development
      • API Gateway for Istio
      • Decentralized Load Balancing
      • BY INDUSTRY
      • Financial Services
      • Healthcare
      • Higher Education
      • Insurance
      • Manufacturing
      • Retail
      • Software & Technology
      • Transportation
      • See all Solutions
      • DOCUMENTATION
      • Kong Konnect
      • Kong Gateway
      • Kong Mesh
      • Kong AI Gateway
      • Kong Event Gateway
      • Kong Insomnia
      • Plugin Hub
      • EXPLORE
      • Blog
      • Learning Center
      • eBooks
      • Reports
      • Demos
      • Customer Stories
      • Videos
      • EVENTS
      • AI + API Summit
      • Webinars
      • User Calls
      • Workshops
      • Meetups
      • See All Events
      • FOR DEVELOPERS
      • Get Started
      • Community
      • Certification
      • Training
      • COMPANY
      • About Us
      • Why Kong?
      • We're Hiring!
      • Press Room
      • Investors
      • Contact Us
      • PARTNER
      • Kong Partner Program
      • SECURITY
      • Trust and Compliance
      • SUPPORT
      • Enterprise Support Portal
      • Professional Services
      • Documentation
      • Press Releases

        Kong Names Bruce Felt as Chief Financial Officer

        Read More
  • Pricing
  • Login
  • Get a Demo
  • Start for Free
Blog
  • AI Gateway
  • AI Security
  • AIOps
  • API Security
  • API Gateway
|
    • API Management
    • API Development
    • API Design
    • Automation
    • Service Mesh
    • Insomnia
    • View All Blogs
  1. Home
  2. Blog
  3. Engineering
  4. Kong Konnect RESTful Admin APIs and AWS AppSync GraphQL Services - Part I: Query
Engineering
September 20, 2023
22 min read

Kong Konnect RESTful Admin APIs and AWS AppSync GraphQL Services - Part I: Query

Claudio Acquaviva
Principal Architect, Kong

GraphQL is a query language to enable applications to fetch data from servers.

In fact, as it isn't tied to any specific database or storage engine, GraphQL can aggregate data from multiple sources to create a natural representation of your data. The representation is a graph. The following image illustrates a typical GraphQL abstraction:

A few common GraphQL use cases include:

  • Composite pattern: Apps can retrieve data from multiple, different storage APIs.
  • API aggregation: GraphQL can be used to combine multiple APIs into a single GraphQL endpoint.
  • No over-fetching or under-fetching: GraphQL brings efficiency and power to mobile applications by offering a single endpoint to query data without multiple requests to the server.
  • Data caching: GraphQL can be used to cache data on the client side to accelerate loading times.
  • Strongly typed schema: All the types supported by the API are specified in the schema in GraphQL Schema Definition Language (SDL).

You can get a bit more familiar with GraphQL by checking out www.graphql.org.

Kong Konnect Objects and APIs

Kong Konnect is an API lifecycle management platform designed from the ground up for the cloud native era and delivered as a service. The management plane (Control Plane) is hosted in the cloud by Kong, while the runtime engine (Data Plane), Kong Gateway is managed by the customers within your preferred network environment.

Kong Konnect Objects

Kong Konnect administrators work with an object model to define their desired traffic management policies. The model comprehends multiple and fundamental objects:

  • Kong Service: This is an abstraction of an existing upstream application.
  • Kong Route: These are added to services to expose and allow access to the underlying application. A Kong Service can have multiple Kong Routes defined.
  • Kong Consumer: This is an entity that makes requests for Kong to proxy. It represents either a user or an external service. A Consumer might have multiple Credentials associated.
  • Kong Plugin: These provide advanced functionality and extend the use of the Kong Gateway, which allows you to add new features to your implementation. Kong Plugins can be applied to Kong Services, Routes, Consumers, and globally. Kong Konnect provides a comprehensive list of plugins to extend the gateway by implementing specific policies
  • Kong Upstream: This represents a virtual hostname and can be used to load balance incoming requests over multiple services, called Targets. An upstream implements health checker and circuit breakers, which are able to enable and disable targets based on their ability or inability to serve requests.
  • There are also other Objects, like Kong Certificates and Keys, SNIs, Vaults, Workspaces, Admins, and Developers.

The model defines some relationships between objects. For example: a Kong Service has multiple Routes and Plugins defined. Likewise, a Kong Upstream defines multiple Targets related to it.

Kong Konnect RESTful Admin APIs and GraphQL model

Kong Konnect provides multiple ways to allow admins to deal with the Kong Objects Lifecycle, including:

  • Konnect Graphical User Interface
  • decK to manage Kong Objects in a declarative fashion
  • RESTful Admin API, a fundamental mechanism for administration purposes

Considering the RESTful Admin API and the Kong object model relationships, a GraphQL query seems a good fit to provide an easier way to manipulate them. That is, with a single GraphQL request, we can query a Kong Service with all its dependencies, including Kong Routes, Plugins, etc.

Moreover, a GraphQL mutation request can also create a new Service along with Routes and Plugins.

In this post, we’ll exercise the Konnect RESTful Admin APIs consumption with AWS AppSync GraphQL Queries. The subsequent parts of this series will focus on the other two operations: Mutations and Subscriptions.

Kong Konnect Authentication

Before starting to send RESTful Admin requests to Kong Konnect, you need to authenticate as an admin. Kong Konnect provides two types of base URLs that are used in Kong Konnect APIs:

  • Global: https://global.api.konghq.com. Used to manage region-agnostic Kong Konnect entities that live in a global database such as Identity Provider (IdP) settings.
  • Region-specific: https://{REGION_CODE}.api.konghq.com. These endpoints are used to manage Konnect entities that live in a specific Kong Konnect region, for example, runtime groups. Service listings are all region-specific entities and therefore must use the region-specific endpoint.

For this blog post, we’re going to use the US region, so the endpoints are located at https://us.api.konghq.com.

Kong Konnect API reference documentation

For specific APIs go to:

  • Identity Management API: Interface for management of users, teams, team memberships, and role assignments
  • Runtime Groups API: Interface for managing runtime groups
  • Portal RBAC API: Interface for portal developers, teams, and RBAC rules
  • Runtime Configuration API: Interface for creating and managing control plane certificates, data plane certificates, and Kong Konnect entities
  • Portal Client API: Interface for building and integrating custom Dev Portal experiences using Kong Konnect API data

Personal access token (PAT)

The recommended method of authentication for Kong Konnect is the personal access token (PAT), which can be obtained from the personal access token page in the Kong Konnect UI. The PAT must be passed in the Authorization header of all requests, for example:

curl -X GET 'https://global.api.konghq.com/v2/users/' --header 'Authorization: Bearer <your_PAT>

Check the documentation to learn how to register to Konnect and generate a PAT.

Runtime Groups, Services, Routes, and Plugins

This blog post will work with the Services, Routes, and Plugins created in the default Runtime Group. Check the documentation to learn more about Runtime Manager and Runtime Groups.Once you have your PAT generated, you can start sending requests to the Kong Konnect Control Plane. For example, you can check your Runtime Groups with:

curl -s -X GET 'https://us.api.konghq.com/v2/runtime-groups' --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb'`

AWS AppSync

GraphQL API creation

We're going to get started creating a GraphQL Schema with fundamental types representing the three Kong Objects: Kong Service, Kong Route, and Kong Plugin. The Schema also defines three queries:- service(name): Service -> returns information about a specific Kong Service

  • services: [Service] -> returns a list of all Kong Services created in a specific Runtime Group.
  • runtimegroups: [RuntimeGroup] -> returns all existing Runtime Groups.**

GraphQL API with Empty Schema

Create your GraphQL API with an empty schema first:aws appsync create-graphql-api --name konnect-controlplane --authentication-type API_KEY --region us-west-1``

The expected output is:

{
    "graphqlApi": {
        "name": "konnect-controlplane",
        "apiId": "ecwpfdz4ozgzde4yboqh3pwyzq",
        "authenticationType": "API_KEY",
        "arn": "arn:aws:appsync:us-west-1:123456789012:apis/ecwpfdz4ozgzde4yboqh3pwyzq",
        "uris": {
            "REALTIME": "wss://gof4ekr4ejhanbwd6rd6vkvkpi.appsync-realtime-api.us-west-1.amazonaws.com/graphql",
            "GRAPHQL": "https://gof4ekr4ejhanbwd6rd6vkvkpi.appsync-api.us-west-1.amazonaws.com/graphql"
        },
        "tags": {},
        "xrayEnabled": false,
        "dns": {
            "REALTIME": "gof4ekr4ejhanbwd6rd6vkvkpi.appsync-realtime-api.us-west-1.amazonaws.com",
            "GRAPHQL": "gof4ekr4ejhanbwd6rd6vkvkpi.appsync-api.us-west-1.amazonaws.com"
        },
        "visibility": "GLOBAL",
        "apiType": "GRAPHQL",
        "owner": "123456789012"
    }
}

You can retrieve the GraphQL API with

aws appsync list-graphql-apis --region us-west-1 | jq -r ".graphqlApis[].apiId"
ecwpfdz4ozgzde4yboqh3pwyzq

Schema Definition Language (SDL)

Now create a konnect.graphql file with our Schema including Types and Queries:


    type Plugin {
     id: ID
     name: String
     instance_name: String
    }



    type Route {
     id: ID
     name: String
     strip_path: Boolean
    paths: [String]
    }



    type RuntimeGroup {
     id: ID
     name: String
    }



    type Service {
     id: ID
     name: String
     protocol: String
     port: String
     runtimeGroupId: ID
     routes: [Route]
     plugins: [Plugin]
    }



    type Query {
     services(runtimeGroupId: ID): [Service]
     service(name: String, runtimeGroupId: ID): Service
     runtimeGroups: [RuntimeGroup]
    }



    schema {
     query: Query
    }

You have to encode the Schema in a base64 format:

    base64 -i konnect.graphql
    dHlwZSBQbHVnaW4gewoJaWQ6IElECgluYW1lOiBTdHJpbmcKCWluc3RhbmNlX25hbWU6IFN0cmluZwp9Cgp0eXBlIFJvdXRlIHsKCWlkOiBJRAoJbmFtZTogU3RyaW5nCglzdHJpcF9wYXRoOiBCb29sZWFuCnBhdGhzOiBbU3RyaW5nXQp9Cgp0eXBlIFJ1bnRpbWVHcm91cCB7CglpZDogSUQKCW5hbWU6IFN0cmluZwp9Cgp0eXBlIFNlcnZpY2UgewoJaWQ6IElECgluYW1lOiBTdHJpbmcKCXByb3RvY29sOiBTdHJpbmcKCXBvcnQ6IFN0cmluZwoJcnVudGltZUdyb3VwSWQ6IElECglyb3V0ZXM6IFtSb3V0ZV0KCXBsdWdpbnM6IFtQbHVnaW5dCn0KCnR5cGUgUXVlcnkgewoJc2VydmljZXMocnVudGltZUdyb3VwSWQ6IElEKTogW1NlcnZpY2VdCglzZXJ2aWNlKG5hbWU6IFN0cmluZywgcnVudGltZUdyb3VwSWQ6IElEKTogU2VydmljZQoJcnVudGltZUdyb3VwczogW1J1bnRpbWVHcm91cF0KfQoKc2NoZW1hIHsKCXF1ZXJ5OiBRdWVyeQp9

Use the base64 string to create the Schema for you GraphQL API:

    aws appsync start-schema-creation --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1 --definition "dHlwZSBQbHVnaW4gewoJaWQ6IElECgluYW1lOiBTdHJpbmcKCWluc3RhbmNlX25hbWU6IFN0cmluZwp9Cgp0eXBlIFJvdXRlIHsKCWlkOiBJRAoJbmFtZTogU3RyaW5nCglzdHJpcF9wYXRoOiBCb29sZWFuCnBhdGhzOiBbU3RyaW5nXQp9Cgp0eXBlIFJ1bnRpbWVHcm91cCB7CglpZDogSUQKCW5hbWU6IFN0cmluZwp9Cgp0eXBlIFNlcnZpY2UgewoJaWQ6IElECgluYW1lOiBTdHJpbmcKCXByb3RvY29sOiBTdHJpbmcKCXBvcnQ6IFN0cmluZwoJcnVudGltZUdyb3VwSWQ6IElECglyb3V0ZXM6IFtSb3V0ZV0KCXBsdWdpbnM6IFtQbHVnaW5dCn0KCnR5cGUgUXVlcnkgewoJc2VydmljZXMocnVudGltZUdyb3VwSWQ6IElEKTogW1NlcnZpY2VdCglzZXJ2aWNlKG5hbWU6IFN0cmluZywgcnVudGltZUdyb3VwSWQ6IElEKTogU2VydmljZQoJcnVudGltZUdyb3VwczogW1J1bnRpbWVHcm91cF0KfQoKc2NoZW1hIHsKCXF1ZXJ5OiBRdWVyeQp9"

Check the Schema Creation process with:

    aws appsync get-schema-creation-status --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1
    {
        "status": "SUCCESS",
        "details": "Successfully created schema with 6 types."
    }

You can list the Types with:


    % aws appsync list-types --api-id ecwpfdz4ozgzde4yboqh3pwyzq --format json --region us-west-1

Or create a SDL file with:

    $ aws appsync get-introspection-schema --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1 --format SDL schema.graphql

AWS AppSync VTL and JavaScript Resolvers

A GraphQL Resolver is a function that converts the GraphQL request and payload to fetch information from the underlying data source with its specific communication mechanism and responds back to the consumer. In our case, the data source is the Konnect Control Plane with its RESTful Admin APIs.

AWS AppSync supports two types of Resolvers:

  • Mapping Templates: written in Apache Velocity Template Language (VTL), they are comprised of request and response mapping templates, which contain transformation and execution logic. 
  • JavaScript: they are defined by their code which defines their execution logic.

For both types of Resolvers, AppSync provides support for multiple data sources including DynamoDB, Lambda functions, Amazon Aurora, Amazon OpenSearch Service. Besides all these data sources, AppSync also supports generic HTTP endpoints, like the ones Kong Konnect uses to expose its RESTful Admin APIs.

Kong Konnect as an AppSync Data Source

All AppSync queries should be sending requests to the Konnect endpoint. So, the very first thing to do is to define an AppSync Data Source based on the endpoint. Note we’re creating an HTTP type Data Source.

    aws appsync create-data-source --api-id ecwpfdz4ozgzde4yboqh3pwyzq --name konnect --type HTTP --http-config '{"endpoint":"https://us.api.konghq.com"}' --region us-west-1

You can check your Data Sources with:

    aws appsync list-data-sources --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1
    {
        "dataSources": [
            {
                "dataSourceArn": "arn:aws:appsync:us-west-1:123456789012:apis/ecwpfdz4ozgzde4yboqh3pwyzq/datasources/konnect",
                "name": "konnect",
                "type": "HTTP",
                "httpConfig": {
                    "endpoint": "https://us.api.konghq.com"
                }
            }
        ]
    }

RuntimeGroup HTTP Resolver

Now, we should be able to create our Resolvers. To get started, let's create a VTL Resolver for the runtimegroups Query field. Notice that the Resolver is expecting the Konnect PAT as a Authorization Bearer token:

    aws appsync create-resolver --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --kind UNIT \
      --type-name Query \
      --field-name runtimeGroups \
      --data-source-name konnect \
      --region us-west-1 \
      --request-mapping-template '{
      "version": "2018-05-29",
      "method": "GET",
      "resourcePath": "/v2/runtime-groups",
      "params":{
          "headers": {
              "Authorization": "$ctx.request.headers.Authorization"
          }
      }
    }' \
      --response-mapping-template '#if ($ctx.error)
     $util.error($ctx.error.message, $ctx.error.type)
    #end
    #if ($ctx.result.statusCode < 200 || $ctx.result.statusCode >= 300)
     $util.error($ctx.result.body, "StatusCode$ctx.result.statusCode")
    #end


    #set($result = [])
    #foreach($item in $util.parseJson($ctx.result.body).data)
     $util.qr($result.add($item))
    #end
    $util.toJson($result)'

The data that came from Konnect is available for the Response Mapping Template in the Resolver Context Result Body($ctx.result.body). Check the AppSync documentation to learn more about Resolver Mapping Template Context.

The code refers to the .data section located inside the Body to produce the actual GraphQL query response. As an example, here's the output for the request sent to Konnect directly:

    curl -s -X GET 'https://us.api.konghq.com/v2/runtime-groups' --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' | jq
    {
      "meta": {
        "page": {
          "total": 1,
          "size": 100,
          "number": 1
        }
      },
      "data": [
        {
          "id": "<your_default_runtime_group_id>",
          "name": "default",
          "description": "default Runtime Group",
          "labels": {},
          "config": {
            "control_plane_endpoint": "https://ffxyzxyz52.us.cp0.konghq.com",
            "telemetry_endpoint": "https://ffxyzxyz52.us.tp0.konghq.com",
            "cluster_type": "CLUSTER_TYPE_HYBRID"
          },
          "created_at": "2022-04-11T22:49:39.316Z",
          "updated_at": "2022-11-10T20:30:15.724Z"
        }
      ]
    }

AppSync API Key

We’re now ready to send our first GraphQL request to AppSync. However, since the default AppSync Authorization mechanism is an API Key, we need to create one.

    $ aws appsync create-api-key --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1
    {
        "apiKey": {
            "id": "da2-qz2cv52vmzbcpfcbmnwrwf66q4",
            "expires": 1692630000,
            "deletes": 1697814000
        }
    }

You can get your API Key with:

    % aws appsync list-api-keys --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1 | jq -r ".apiKeys[].id"
    da2-qz2cv52vmzbcpfcbmnwrwf66q4

Send a Request

Let's send a request to AppSync. First of all, get the public AppSync API endpoint:

    % aws appsync list-graphql-apis --region us-west-1 | jq -r ".graphqlApis[].uris.GRAPHQL"
    https://gof4ekr4ejhanbwd6rd6vkvkpi.appsync-api.us-west-1.amazonaws.com/graphql

Now, send a request with curl. Notice we're injecting in the request both AppSync API Key and Kong Konnect PAT. The PAT is injected as a Bearer token, as the Resolver is expecting.

    curl -s --request POST \
      --url https://gof4ekr4ejhanbwd6rd6vkvkpi.appsync-api.us-west-1.amazonaws.com/graphql \
      --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' \
      --header 'x-api-key: da2-qz2cv52vmzbcpfcbmnwrwf66q4' \
      --data '
    {"query": "query GetRuntimeGroups{runtimeGroups {id name}}", 
    "operationName":"GetRuntimeGroups", 
    "variables":{}}' | jq

The output should be like this:

    {
      "data": {
        "runtimegroups": [
          {
            "id": "<your_default_runtime_group_id>",
            "name": "default"
          }
        ]
      }
    }

The data that came from Kong Konnect is available for the Response Mapping Template in the Resolver Context Result Body($ctx.result.body). You can check them by submitting a request directly to Konnect:


    curl -s -X GET 'https://us.api.konghq.com/v2/runtime-groups' --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' | jq
    {
      "meta": {
        "page": {
          "total": 1,
          "size": 100,
          "number": 1
        }
      },
      "data": [
        {
          "id": "<your_default_runtime_group_id>",
          "name": "default",
          "description": "default Runtime Group",
          "labels": {},
          "config": {
            "control_plane_endpoint": "https://ffxyzxyz52.us.cp0.konghq.com",
            "telemetry_endpoint": "https://ffxyzxyz52.us.tp0.konghq.com",
            "cluster_type": "CLUSTER_TYPE_HYBRID"
          },
          "created_at": "2022-04-11T22:49:39.316Z",
          "updated_at": "2022-11-10T20:30:15.724Z"
        }
      ]
    }

Services HTTP Resolver

Let's create a second VTL Resolver, now for the services Query field. As you can see, the Resolver definition is quite similar to the previous one. The main difference is that this time, the Resolver expects the RuntimeGroup Id as a context parameter.

    aws appsync create-resolver --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --kind UNIT \
      --type-name Query \
      --field-name services \
      --data-source-name konnect \
      --region us-west-1 \
      --request-mapping-template '{
      "version": "2018-05-29",
      "method": "GET",
      "resourcePath": "/v2/runtime-groups/$ctx.args.runtimeGroupId/core-entities/services",
      "params":{
          "headers": {
              "Authorization": "$ctx.request.headers.Authorization"
          }
      }
    }
    ' \
      --response-mapping-template '#if ($ctx.error)
     $util.error($ctx.error.message, $ctx.error.type)
    #end
    #if ($ctx.result.statusCode < 200 || $ctx.result.statusCode >= 300)
     $util.error($ctx.result.body, "StatusCode$ctx.result.statusCode")
    #end


    #set($result = [])
    #foreach($item in $util.parseJson($ctx.result.body).data)
     $util.qr($result.add($item))
    #end
    $util.toJson($result)'

Send a Request

Use the same endpoint and AppSync API Key to send a new request. Note we’re passing the RuntimeGroup id we got from the previous request as a parameter to the Query:

    curl -s --request POST \
      --url https://gof4ekr4ejhanbwd6rd6vkvkpi.appsync-api.us-west-1.amazonaws.com/graphql \
      --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' \
      --header 'x-api-key: da2-qz2cv52vmzbcpfcbmnwrwf66q4' \
      --data '
    {"query": "query GetServices($runtimegroupid: ID) {services(runtimeGroupId: $runtimegroupid) {id name}}",
    "operationName": "GetServices",
    "variables":{"runtimegroupid": "<your_default_runtime_group_id>"}}' | jq

You should get a list of all current Kong Services:

    {
      "data": {
        "services": [
          {
            "id": "3ab5c506-2e94-41e1-8c6f-6f37f2e68a43",
            "name": "service2"
          },
          {
            "id": "5c44ecb3-dd8d-4fd9-bd76-5beb564e15ef",
            "name": "service1"
          }
        ]
      }
    }

You can check the AWS AppSync console with the Schema and Resolvers defined:

Service Resolver

With the two fundamental Resolvers in place, let's work with the third one. The service Resolver takes as parameters a service name and the runtime groups the service belongs to. The output includes the two main dependencies of the service: Kong Routes and Kong Plugins.

This time, we’re going to use the APPSYNC_JS Runtime to get a better JavaScript implementation for our Resolver. The AWS AppSync JavaScript Resolvers Overview documentation page provides a nice diagram with the anatomy of a resolver.

The GraphQL Query Request Structure

To get a better understanding of the Resolver implementation, we’re going to take an evolving approach. That is, considering the full Query, we’re going to solve it breaking with three independent steps: (1) Service, (2) Routes and (3) Plugins.

    query GetService($service_name: String, $runtimegroupid: ID) {
     service(name: $service_name, runtimegroupid: $runtimegroupid) {
     id
     name
     protocol
     port
     runtimegroupid
     routes {
     id
     name
     strip_path
     paths
     }
     plugins {
     id
     name
     instance_name
     }
     }
    }

Send a Request

Let's send the Request with no Resolver first:

    curl -s --request POST \
      --url https://gof4ekr4ejhanbwd6rd6vkvkpi.appsync-api.us-west-1.amazonaws.com/graphql \
      --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' \
      --header 'x-api-key: da2-qz2cv52vmzbcpfcbmnwrwf66q4' \
      --data '{"query":"query GetService($service_name: String, $runtimegroupid: ID)                                     {service(name: $service_name, runtimeGroupId: $runtimegroupid)              {id name protocol port runtimeGroupId                                           routes {id name strip_path paths}                                     plugins {id name instance_name}}}",       "operationName":"GetService",
    "variables":{"service_name":"service2","runtimegroupid":"<your_default_runtime_group_id>"}}' | jq

Since we don't have any Resolver defined, the output would look like this:

    {
      "data": {
        "service": null
      }
    }

Service Section

Let's solve the Service section creating a function to be used by our Resolver. Create a file named service.js with an AppSync Function to process the section. The function takes the two query parameters, runtimegroupid and name to consume the specific Konnect endpoint. The Resolver builds the result based on the Konnect response, adding the runtimegroupid to it, so the functions responsible for the Routes and Plugin Section can use it.

service.js

    import { util } from '@aws-appsync/utils';


    export function request(ctx) {
        
        var path = "/v2/runtime-groups/"
        path = path.concat(ctx.args.runtimeGroupId)
        path = path.concat("/core-entities/services/")
        const serviceName = ctx.args.name
        
        const fullPath = path.concat(serviceName)
        
        return {
            version: "2018-05-29",
            method: "GET",
            resourcePath: fullPath,
            params: {
                headers: {
                    Authorization: ctx.request.headers.Authorization
                }
            }
        };
    }


    export function response(ctx) {
        var result = JSON.parse(ctx.result.body);
        result.runtimeGroupId = ctx.args.runtimeGroupId
        return result;
    }

Create the AppSync function with:

    aws appsync create-function --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --region us-west-1 \
      --name "service" \
      --code "file://service.js" \
      --data-source-name "konnect" \
      --runtime name=APPSYNC_JS,runtimeVersion=1.0.0

Fetch and save your function Id with:

    % aws appsync list-functions --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1 | jq -r ".functions[].functionId"
    dwyuatzixvddrip45elgjnojyu

basic_resolver.js

The Resolver also needs basic request and response functions. Create another file named basic_resolver.js with the functions:

    import {util} from '@aws-appsync/utils';
    export function request(ctx) {
        return {};
    }
    export function response(ctx) {
        return ctx.prev.result;
    }

Create the Resolver

Now let's create the Resolver with all functions we've created so far:

    aws appsync create-resolver --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --region us-west-1 \
      --kind PIPELINE \
      --type-name Query \
      --field-name service \
      --pipeline-config functions=dwyuatzixvddrip45elgjnojyu \
      --code "file://basic_resolver.js" \
      --runtime name=APPSYNC_JS,runtimeVersion=1.0.0

Send a Request

If we send the same Request again, we should be able to see the Service Section with its attributes. As expected the Routes and Plugins Sections are still empty.

    {
      "data": {
        "service": {
          "id": "3ab5c506-2e94-41e1-8c6f-6f37f2e68a43",
          "name": "service2",
          "protocol": "http",
          "port": "80",
          "runtimeGroupId": "<your_default_runtime_group_id>",
          "routes": null,
          "plugins": null
        }
      }
    }

Routes Resolver

Now, let's solve the Routes Section with another Resolver. We have to consider two things this time:

  • The Routes Section is nested inside the Query structure.
  • Since it's a nested section, the Resolver is related to the Service Field, not to the Query Field as the Service Section is.

dependencies.js

To solve the Routes Section we’re going to create a function that is able to get both Services dependencies, Kong Routes, and Kong Plugins. The function takes the runtimegroupid and serviceid from its parent Service's Resolver available in ctx.source Context field. It also expects a dependency field available in the ctx.stash. Please check the AppSync documentation to learn more about the Resolver Context Object.


    import { util } from '@aws-appsync/utils';


    export function request(ctx) {
        const { dependency } = ctx.stash
        const { runtimeGroupId } = ctx.source
        const { id } = ctx.source


        var path = "/v2/runtime-groups/"
        path = path.concat(runtimeGroupId)
        path = path.concat("/core-entities/services/")
        path = path.concat(id)
        path = path.concat("/")
        const fullPath = path.concat(dependency)


        return {
            version: "2018-05-29",
            method: "GET",
            resourcePath: fullPath,
        
            params: {
                headers: {
                    Authorization: ctx.request.headers.Authorization
                }
            }
        };
    }


    export function response(ctx) {
        return JSON.parse(ctx.result.body).data;
    }

Create the function with:

    aws appsync create-function --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --region us-west-1 \
      --name "dependencies" \
      --code "file://dependencies.js" \
      --data-source-name "konnect" \
      --runtime name=APPSYNC_JS,runtimeVersion=1.0.0

Get and save the function id:

    aws appsync list-functions --api-id ecwpfdz4ozgzde4yboqh3pwyzq --region us-west-1 | jq -r '.functions[] | select (.name == "dependencies") | .functionId'
    aoocqc75i5by5lzjgc5pssq7j4

routes.js

Now we need to create a specific routes field specific Resolver. The main responsibility of the function is to set the ctx.stash.dependency field, expected by the dependencies function.

    import {util} from '@aws-appsync/utils';
    export function request(ctx) {
        ctx.stash.dependency = "routes";
        return {};
    }
    export function response(ctx) {
        return ctx.prev.result;
    }

Create the Resolver

The Resolver is ready to be created. The Resolver pipeline includes the previously created dependency function id:

    aws appsync create-resolver --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --region us-west-1 \
      --kind PIPELINE \
      --type-name Service \
      --field-name routes \
      --pipeline-config functions=aoocqc75i5by5lzjgc5pssq7j4 \
      --code "file://routes.js" \
      --runtime name=APPSYNC_JS,runtimeVersion=1.0.0

Send a Request

Send the same request we did before to get the Kong Routes defined for our Service. The output should look like this. Notice that we still need to solve the Plugins Section.

    {
      "data": {
        "service": {
          "id": "3ab5c506-2e94-41e1-8c6f-6f37f2e68a43",
          "name": "service2",
          "protocol": "http",
          "port": "80",
          "runtimeGroupId": "<your_default_runtime_group_id>",
          "routes": [
            {
              "id": "4c0dc44a-3ef0-4a81-b422-ae4901ebb136",
              "name": "route2",
              "strip_path": true,
              "paths": [
                "/route2"
              ]
            },
            {
              "id": "f640b18c-aa9f-486c-9bc0-f709e1bc539e",
              "name": "route22",
              "strip_path": true,
              "paths": [
                "/path2",
                "/p2"
              ]
            }
          ],
          "plugins": null
        }
      }
    }

Plugins Resolver

The Plugin Resolver works exactly the same way the Routes Resolver does. In fact, it's going to add the same dependencies function into its pipeline. The only difference is that the Resolver has to set the ctx.stash.dependency with a different value, in this case, "plugins"

plugins.js


    import {util} from '@aws-appsync/utils';
    export function request(ctx) {
        ctx.stash.dependency = "plugins";
        return {};
    }


    export function response(ctx) {
        return ctx.prev.result;
    }

Create the Resolver

Again, the command to create the Resolver is very similar to the one we ran for the Routes Resolver.

    aws appsync create-resolver --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --region us-west-1 \
      --kind PIPELINE \
      --type-name Service \
      --field-name plugins \
      --pipeline-config functions=aoocqc75i5by5lzjgc5pssq7j4 \
      --code "file://plugins.js" \
      --runtime name=APPSYNC_JS,runtimeVersion=1.0.0

Send a Request

Finally, our request's output should include all Plugins enabled to our Service:


    {
      "data": {
        "service": {
          "id": "3ab5c506-2e94-41e1-8c6f-6f37f2e68a43",
          "name": "service2",
          "protocol": "http",
          "port": "80",
          "runtimeGroupId": "<your_default_runtime_group_id>",
          "routes": [
            {
              "id": "4c0dc44a-3ef0-4a81-b422-ae4901ebb136",
              "name": "route2",
              "strip_path": true,
              "paths": [
                "/route2"
              ]
            },
            {
              "id": "f640b18c-aa9f-486c-9bc0-f709e1bc539e",
              "name": "route22",
              "strip_path": true,
              "paths": [
                "/path2",
                "/p2"
              ]
            }
          ],
          "plugins": [
            {
              "id": "e909cf77-fa91-4b44-8a20-c38d1e14de98",
              "name": "key-auth",
              "instance_name": "key1"
            }
          ]
        }
      }
    }

Kong Konnect GraphQL Rate Limiting Advanced Plugin

With all AppSync Resolvers in place we’re able to send as many GraphQL requests as we want. In a real-world scenario, we should have some policies to control that.

In this section, we’re going to deploy a Kong Konnect Data Plane in front of the AWS AppSync in order to enforce some Rate Limiting policies to control the number of requests AppSync should process in a specific period of time.

The final topology would look like this:

In fact, this is a circular deployment where the Konnect Control Plane manages not just the Data Plane but also the Kong Objects (Services, Routes, and Plugins) responsible for controlling the AppSync exposure.

Konnect Runtime Instance

A Konnect Runtime Instance is a single self-managed instance of Kong Gateway that functions as a Data Plane. In Kong Konnect, Runtime Instances are part of Runtime Groups and expose all Kong Objects (Services, Routes, Consumers, Plugins, etc) defined in the Runtime Group it belongs to.

Konnect Runtime Instance deployment

The first step is to deploy a Konnect Runtime Instance. Konnect supports a variety of platforms to run Runtime Instances including Kubernetes, Docker, Linux-based OSes, etc. Please check the Installation Options documentation page to learn more about Runtime Instances.

For this blog post, we’re going to deploy our Runtime Instance on an Amazon EKS Cluster. Click on the Amazon EKS documentation page to learn how to create a cluster with eksctl, the official CLI for Amazon EKS.

After getting your EKS Cluster up and running, log in to Kong Konnect.

Go to "Runtime Manager" -> "default" -> "+ New Runtime Instance" -> "Kubernetes". Click on "Generate Certificate" and save the certificate and private key into corresponding files. For example:

Cluster Certificate: tls.crt

Certificate Key: tls.key

Injecting key and digital certificate

Create a Kubernetes namespace for the data plane.

    kubectl create namespace kong-dp

Create secrets to digital certificate and key.

    kubectl create secret tls kong-cluster-cert --cert=./tls.crt --key=./tls.key -n kong-dp

Konnect Data Plane

Copy and paste this text into a file called values.yaml on your local machine.

    image:
      repository: kong/kong-gateway
      tag: "3.4"


    secretVolumes:
    - kong-cluster-cert


    admin:
      enabled: false


    env:
      role: data_plane
      database: "off"
      cluster_mtls: pki
      cluster_control_plane: <your_control_plane_endpoint>:443
      cluster_server_name: <your_control_plane_endpoint>
      cluster_telemetry_endpoint: <your_telemetry_server>:443
      cluster_telemetry_server_name: <your_telemetry_server>
      cluster_cert: /etc/secrets/kong-cluster-cert/tls.crt
      cluster_cert_key: /etc/secrets/kong-cluster-cert/tls.key
      lua_ssl_trusted_certificate: system
      konnect_mode: "on"
      vitals: "off"


    ingressController:
      enabled: false
      installCRDs: false

Use Helm to deploy the data plane with endpoints provided by Kong Konnect control plane:

    helm install kong-dp kong/kong -n kong-dp --values ./values.yaml

Checking the installation

Use kubectl to check the Kubernetes deployment

    $ kubectl get deployment --all-namespaces
    NAMESPACE     NAME           READY   UP-TO-DATE   AVAILABLE   AGE
    kong-dp       kong-dp-kong   1/1     1            1           44s
    kube-system   coredns        2/2     2            2           27m


    $ kubectl get pod --all-namespaces
    NAMESPACE     NAME                            READY   STATUS    RESTARTS   AGE
    kong-dp       kong-dp-kong-7b96b8fc8b-cc45x   1/1     Running   0          70s
    kube-system   aws-node-jkc4t                  1/1     Running   0          18m
    kube-system   coredns-f9c975b5b-94wwn         1/1     Running   0          27m
    kube-system   coredns-f9c975b5b-b247x         1/1     Running   0          27m
    kube-system   kube-proxy-lbbbg                1/1     Running   0          18m


    kubectl get service kong-dp-kong-proxy -n kong-dp --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}'
    <your_runtime_instance_elb_address>

You should be able to see the new runtime in Kong Konnect control plane:

Send a request to the runtime

Use the load balancer provisioned by AWS to send requests to the data plane:

    # http <your_runtime_instance_elb_address>
    HTTP/1.1 404 Not Found
    Connection: keep-alive
    Content-Length: 52
    Content-Type: application/json; charset=utf-8
    Date: Tue, 15 Aug 2023 13:55:41 GMT
    Server: kong/3.4.0.0-enterprise-edition
    X-Kong-Response-Latency: 0


    {
        "message": "no Route matched with those values"
    }

Kong Gateway Service and Route

Now, let's create a new Kong Service and Route to expose the AWS AppSync endpoint with the Konnect Runtime Instance. The Kong Service will be created in the same Runtime Group where our existing Kong Objects, in our case, the default Runtime Group. You can use the Konnect GUI if you like or, again, the Konnect RESTful API:

Kong Gateway Service

The new graphqlservice Kong Gateway Service is based on the AWS AppSync endpoint. Notice that we inject the Konnect PAT in the request:

    http https://us.api.konghq.com/v2/runtime-groups/<your-default-runtime-group-id>/core-entities/services name=graphqlservice \
    url='https://gof4ekr4ejhanbwd6rd6vkvkpi.appsync-api.us-west-1.amazonaws.com/graphql' \
    Authorization:"Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb"

Get your new Gateway Service Id with:

    http https://us.api.konghq.com/v2/runtime-groups/<your-default-runtime-group-id>/core-entities/services \
    Authorization:"Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb" | jq -r '.data[] | select (.name == "graphqlservice") | .id'
    020f88b6-3f5a-445e-813d-c73a766f2ea0

Kong Route

Use the Service Id to define the Kong Route:

    http https://us.api.konghq.com/v2/runtime-groups/<your-default-runtime-group-id>/core-entities/services/020f88b6-3f5a-445e-813d-c73a766f2ea0/routes name='graphqlroute' paths:='["/graphql"]' Authorization:"Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb"

You can check the Gateway and Route in the Konnect GUI:

AWS Lambda as the AppSync Authorization Method

We’re going to explore another AppSync Authorization Method, replacing the existing one based on API Key to another using an AWS Lambda Function. With this, we’re able to remove the x-api-key header from the request, leaving both AppSync and Konnect Control Plane to check the Bearer token with the PAT instead.

The AWS Lambda Function

First of all, create an IAM role to be used by the Lambda function. Create a role.json file with:


#### role.json

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
          "Service": [
            "lambda.amazonaws.com"
          ]
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }

Create the IAM Role with the following command:

    aws iam create-role \
      --role-name konnect_appsync \
      --assume-role-policy-document file://role.json

AWS Lambda Function

Create another file, named isauthorized.py, with the actual function code written in Python. Notice the function is extremely simple, just checking if the request has a Bearer token injected. Please, bear in mind the Authorization logic can go as complex as we want.

    def lambda_handler(event, context):
      token = event.get('authorizationToken')
      if 'Bearer' in token:
        return {
          'isAuthorized': True
        }

Zip the file:

    zip isauthorized.zip isauthorized.py

Create the konnectIsAuthorized Lambda function:

    aws lambda create-function \
      --region us-west-1 \
      --function-name konnectIsAuthorized \
      --zip-file fileb://isauthorized.zip \
      --runtime python3.11 \
      --role arn:aws:iam::123456789012:role/konnect_appsync \
      --handler isauthorized.lambda_handler

You can invoke the function to test it:

    % aws lambda invoke \
      --region us-west-1 \
      --function-name konnectIsAuthorized \
      --cli-binary-format raw-in-base64-out \
      --payload '{"authorizationToken": "Bearer value"}' \
      outputfile.txt


    % cat outputfile.txt 
    {"isAuthorized": true}

Change the GraphQL API Authorization Mode

Now we’re ready to replace the existing authorization mode with the Lambda function based one:

    aws appsync update-graphql-api \
      --api-id ecwpfdz4ozgzde4yboqh3pwyzq \
      --name konnect-controlplane \
      --region us-west-1 \
      --authentication-type AWS_LAMBDA \
      --lambda-authorizer-config "{ \"authorizerResultTtlInSeconds\": 300, \"authorizerUri\": \"arn:aws:lambda:us-west-1:123456789012:function:konnectIsAuthorized\" }"

Send new Requests to the Konnect Runtime Instance

We should get a similar response if we send a request to the Runtime Instance. For a simple test, the curl command uses the -k option to accept the Konnect Self-Signed Digital Certificate. Moreover, we don't need to inject the x-api-key AppSync Header, since our Lambda function is responsible for the AppSync Authorization mode.

    curl -s -k --request POST \
      --url http://ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com/graphql \
      --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' \
      --header 'Content-Type: application/json' \
      --data '{"query":"query GetService($service_name: String, $runtimegroupid: ID)                                     {service(name: $service_name, runtimeGroupId: $runtimegroupid)              {id name protocol port runtimeGroupId                                           routes {id name strip_path paths}                                     plugins {id name instance_name}}}",       "operationName":"GetService",
    "variables":{"service_name":"service2","runtimegroupid":"<your_runtime_group_id>"}}' | jq

GraphQL Rate Limiting Advanced Plugin

So far, we have deployed the Konnect Runtime Instance in front of the AWS AppSync API. However, there's no policy controlling how the GraphQL API has been consumed. Now, it’s time to enable the GraphQL Rate Limiting Advanced Plugin to our Kong Service.

The GraphQL Rate Limiting Advanced plugin provides rate limiting for GraphQL queries. The GraphQL Rate Limiting plugin extends the Rate Limiting Advanced plugin.

Due to the nature of client-specified GraphQL queries, the same HTTP request to the same URL with the same method can vary greatly in cost depending on the semantics of the GraphQL operation in the body. A common pattern to protect your GraphQL API is then to analyze and assign costs to incoming GraphQL queries and rate limit the consumer’s cost for a given time window.

GraphQL query costs are evaluated by introspecting the endpoint’s GraphQL schema and applying cost decoration to parts of the schema tree. Learn more about GraphQL Queries Cost Management on the specific GraphQL Rate Limiting Advanced Plugin documentation page.

Install Redis

As a best practice for Runtime Instance deployments, we should externalize the limits to an external Redis infrastructure. In this sense, all Kubernetes replicas of the deployment will rely on the same limits. To support a configuration like this, the GraphQL Rate Limiting Advanced Plugin provides multiple strategies, including Redis.

You can have your Redis infrastructure running on a totally and independent environment, for example, leveraging the AWS ElastiCache for Redis. To have a simpler deployment, you can run Redis in the same EKS Cluster with this declaration.

    kubectl create namespace redis


    cat <<EOF | kubectl apply -n redis -f -
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: redis
      labels:
        app: redis
    spec:
      selector:
        matchLabels:
          app: redis
      replicas: 1
      template:
        metadata:
          labels:
            app: redis
        spec:
          containers:
          - name: redis
            image: redis
            ports:
            - containerPort: 6379
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: redis
      labels:
        app: redis
    spec:
      ports:
      - port: 6379
        targetPort: 6379
      selector:
        app: redis
    EOF

Apply GraphQL Rate Limiting Advanced plugin to the Service

Finally, let's enable the plugin to our Service. Notice we’re using a simple limit and window_size to exercise the rate-limiting policy. Also, we can tell where the Redis endpoint is using the Kubernetes Service FQDN, redis.redis.svc.cluster.local.

    curl -X POST https://us.api.konghq.com/v2/runtime-groups/<your-runtime-group-id>/core-entities/services/020f88b6-3f5a-445e-813d-c73a766f2ea0/plugins \
      --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' \
      --header 'Content-Type: application/json' \
      --data '{
      "name": "graphql-rate-limiting-advanced",
      "instance_name": "graphql-rla",
      "config": {
        "limit": [100],
        "window_size": [30],
        "sync_rate" : 0,
        "strategy": "redis",
        "redis": {
          "host": "redis.redis.svc.cluster.local",
          "port": 6379
        }
      }
    }'

Send new Requests to the Runtime Instance

This time we’re using the -v option for curl to see the Rate Limiting related Headers: X-Gql-Query-Cost, X-RateLimit-Limit-30 and X-RateLimit-Remaining-30.

    curl -s -v -k --request POST \
      --url http://ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com/graphql \
      --header 'Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb' \
      --header 'Content-Type: application/json' \
      --data '{"query":"query GetService($service_name: String, $runtimegroupid: ID)                                     {service(name: $service_name, runtimeGroupId: $runtimegroupid)              {id name protocol port runtimeGroupId                                           routes {id name strip_path paths}                                     plugins {id name instance_name}}}",       "operationName":"GetService",
    "variables":{"service_name":"service2","runtimegroupid":"<your_runtime_group_id>"}}' | jq


    *   Trying 13.57.151.101:80...
    * Connected to ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com (13.57.151.101) port 80 (#0)
    > POST /graphql HTTP/1.1
    > Host: ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com
    > User-Agent: curl/8.1.2
    > Accept: */*
    > Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb
    > Content-Type: application/json
    > Content-Length: 501
    > 
    } [501 bytes data]
    < HTTP/1.1 200 OK
    < Content-Type: application/json;charset=UTF-8
    < Content-Length: 486
    < Connection: keep-alive
    < X-Gql-Query-Cost: 17
    < X-RateLimit-Limit-30: 100
    < X-RateLimit-Remaining-30: 83
    < Date: Wed, 16 Aug 2023 17:46:00 GMT
    < x-amzn-RequestId: 1b9b9cb1-e8bf-4749-bc48-7e9df514aed7
    < x-amzn-appsync-TokensConsumed: 1
    < X-Cache: Miss from cloudfront
    < Via: kong/3.4.0.0-enterprise-edition
    < X-Amz-Cf-Pop: SFO5-P2
    < X-Amz-Cf-Id: O8HE8qRuPKMrhMZOQ5ZM4g3853n-ALFV5weCMoJuqqDbxP3XGs0f4A==
    < X-Kong-Upstream-Latency: 794
    < X-Kong-Proxy-Latency: 38
    < 
    { [486 bytes data]
    * Connection #0 to host ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com left intact
    {
      "data": {
        "service": {
          "id": "3ab5c506-2e94-41e1-8c6f-6f37f2e68a43",
          "name": "service2",
          "protocol": "http",
          "port": "80",
          "runtimeGroupId": "5cd89683-2fa4-4664-b35d-646142abe710",
          "routes": [
            {
              "id": "4c0dc44a-3ef0-4a81-b422-ae4901ebb136",
              "name": "route2",
              "strip_path": true,
              "paths": [
                "/route2"
              ]
            },
            {
              "id": "f640b18c-aa9f-486c-9bc0-f709e1bc539e",
              "name": "route22",
              "strip_path": true,
              "paths": [
                "/path2",
                "/p2"
              ]
            }
          ],
          "plugins": [
            {
              "id": "e909cf77-fa91-4b44-8a20-c38d1e14de98",
              "name": "key-auth",
              "instance_name": "key1"
            }
          ]
        }
      }
    }

If you keep on sending requests to Konnect Runtime Instance you'll get a 429 error code:

    *   Trying 13.57.151.101:80...
    * Connected to ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com (13.57.151.101) port 80 (#0)
    > POST /graphql HTTP/1.1
    > Host: ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com
    > User-Agent: curl/8.1.2
    > Accept: */*
    > Authorization: Bearer kpat_Ce1jPvtQkmttTFQ5vGJbF25la8y73imPhme4seJwdXPQ2B8Xb
    > Content-Type: application/json
    > Content-Length: 501
    > 
    } [501 bytes data]
    < HTTP/1.1 429 Too Many Requests
    < Date: Wed, 16 Aug 2023 17:48:40 GMT
    < Content-Type: application/json; charset=utf-8
    < Connection: keep-alive
    < X-Gql-Query-Cost: 17
    < X-RateLimit-Limit-30: 100
    < X-RateLimit-Remaining-30: 0
    < Content-Length: 37
    < X-Kong-Response-Latency: 1
    < Server: kong/3.4.0.0-enterprise-edition
    < 
    { [37 bytes data]
    * Connection #0 to host ac3381a02aee0472b9c76bd9c85631e8-1490051267.us-west-1.elb.amazonaws.com left intact
    {
      "message": "API rate limit exceeded"
    }

Conclusion

Kong Konnect simplifies API management and improves security for all services including GraphQL infrastructure. Try it for free today!

This blog post described Kong Konnect’s capabilities to:

  1. Embedded its RESTful Admin API to support GraphQL for the Konnect Control Plane administration.
  2. Expose and protect an AWS AppSync API with enterprise-wide policies implemented by the Konnect Runtime Instance.

The next blog post of this series will describe how to implement other GraphQL operations: Mutations and Subscriptions. Stay tuned!

GraphQLAWSCloud

More on this topic

Videos

Best Practices and Architectural Patterns to Build a Cloud Fabric Layer Using Kong on AWS

Videos

Lessons Learned in Migrating to a Modern API Management Solution on AWS from Netscaler

See Kong in action

Accelerate deployments, reduce vulnerabilities, and gain real-time visibility. 

Get a Demo
Topics
GraphQLAWSCloud
Claudio Acquaviva
Principal Architect, Kong

Recommended posts

Get Gravitas and Go Amazonian: Kong Validated for AWS Graviton3, Amazon Linux 2023 OS

EngineeringJune 26, 2023

Today, we're thrilled to announce that Kong Enterprise and Kong Konnect Data Planes are now validated to run on AWS Graviton3 processors and Amazon Linux 2023 OS. As an APN Advanced Tier Partner of AWS, we were delighted to have the opportunity to

Claudio Acquaviva

Reach for the Clouds: A Crawl/Walk/Run Strategy with Kong and AWS

EngineeringApril 24, 2023

I once heard someone say, "What the cloud migration strategies lack at the moment is a methodology to Lift-and-Shift connections to the cloud." Let's digest that. In today's landscape, maintaining a competitive edge and delivering a high-quality cus

Danny Freese

Maintain Your Kong Gateway Audit Log Trail in AWS CloudTrail Lake

EngineeringFebruary 7, 2023

A critical and challenging requirement for many organizations is meeting audit and compliance obligations. The goal of compliance is to secure business processes, sensitive data, and monitor for unauthorized activities or breaches. AWS CloudTrail

Danny Freese

Kong Data Plane Life Cycle With AWS Cloud Development Kit

EngineeringJanuary 28, 2022

From the modern application platform perspective, products should allow architects and DevOps teams to support dynamic topologies. That means a multi-platform capability is required but not sufficient. In fact, for several reasons, companies are loo

Claudio Acquaviva

Building Kong Clusters in AWS with the Terraform API Gateway Module

EngineeringJanuary 15, 2021

We created the Terraform API gateway module to help you follow DevOps best practices while implementing Kong using infrastructure as code (IaC). Terraform is an open source tool that allows you to implement IaC using a declarative declaration defini

Kong

Configuring AWS GuardDuty with Lambda for Slack Notifications

EngineeringNovember 13, 2019

At Kong, we leverage many tools to protect our services and customers. Terraform from HashiCorp allows us to automate the process with Infrastructure as Code (IaC). Another important tool is Amazon Web Services (AWS) GuardDuty , a continuous mo

Dennis Kelly

AI Agent with Strands SDK, Kong AI/MCP Gateway & Amazon Bedrock

EngineeringJanuary 12, 2026

In one of our posts, Kong AI/MCP Gateway and Kong MCP Server technical breakdown, we described the new capabilities added to Kong AI Gateway to support MCP (Model Context Protocol). The post focused exclusively on consuming MCP server and MCP tool

Jason Matis

Ready to see Kong in action?

Get a personalized walkthrough of Kong's platform tailored to your architecture, use cases, and scale requirements.

Get a Demo
Powering the API world

Increase developer productivity, security, and performance at scale with the unified platform for API management, AI gateways, service mesh, and ingress controller.

Sign up for Kong newsletter

    • Platform
    • Kong Konnect
    • Kong Gateway
    • Kong AI Gateway
    • Kong Insomnia
    • Developer Portal
    • Gateway Manager
    • Cloud Gateway
    • Get a Demo
    • Explore More
    • Open Banking API Solutions
    • API Governance Solutions
    • Istio API Gateway Integration
    • Kubernetes API Management
    • API Gateway: Build vs Buy
    • Kong vs Postman
    • Kong vs MuleSoft
    • Kong vs Apigee
    • Documentation
    • Kong Konnect Docs
    • Kong Gateway Docs
    • Kong Mesh Docs
    • Kong AI Gateway
    • Kong Insomnia Docs
    • Kong Plugin Hub
    • Open Source
    • Kong Gateway
    • Kuma
    • Insomnia
    • Kong Community
    • Company
    • About Kong
    • Customers
    • Careers
    • Press
    • Events
    • Contact
    • Pricing
  • Terms
  • Privacy
  • Trust and Compliance
  • © Kong Inc. 2026