loading...
AWS Heroes

Five reasons you should consider AppSync over API Gateway

theburningmonk profile image Yan Cui Originally published at lumigo.io ・8 min read

Since its inception in 2015, GraphQL has enjoyed a meteoric rise in popularity and a huge community has grown around it.

Alt Text

AppSync is a fully managed AWS serverless service for real-time data queries, synchronization, and communications. In AppSync, AWS has a GraphQL-as-a-Service offering that makes it easy to build scalable and resilient GraphQL APIs in the cloud. You don’t need to run any servers yourself. Simply configure your GraphQL resolvers, and you have a GraphQL API that can scale to millions of users and offers multi-AZ redundancy out-of-the-box.

Simply put, AppSync is to GraphQL what API Gateway is to REST APIs.

In recent months I have been working extensively with AppSync and have grown to love it. Not only does it make it easy to build scalable GraphQL APIs, but it also makes short work of difficult tasks in API Gateway.

With all else being equal, here are my top five reasons you should consider using AppSync instead of API Gateway for your next project.

1. Cognito Group-based Authorization

Imagine you’re building a CMS app, where super admins can create universities and designate university admins that can administer their own profile.

To implement this, you can use Cognito user groups to control access to the different API endpoints. e.g.

  • POST /universities is only accessible by users in the Admin group to create new universities and set up their initial profile.

  • Post /university/my is only accessibly by users in the University group to update their profile.

This is possible with API Gateway, but it takes a lot of work as you can see from the official guide:

  1. add user groups
  2. assign an IAM role to each group to control which endpoints users in the group can access
  3. assign precedence to groups because a user can belong to multiple groups, and you need to resolve to one IAM role
  4. submit the ID token from Cognito when you send a request to API Gateway
  5. write a custom Lambda authorizer function (that’s right, you can’t use a Cognito authorizer!) to generate the correct policy based on the ID token

It’s complicated.

In contrast, this is all I need to implement group-based authorization in AppSync.

Alt Text

It’s effortless and self-explanatory.

Learn more about AWS serverless in our guide: AWS Step Functions – Limits, Use Cases, Best Practices

2. Request and Response Validation

One of the under-appreciated features of API Gateway is the fact that it can perform request validation for you. Requests that fail the schema validation do not incur costs. It’s a good way to protect yourself against malicious attackers that naively throw junk requests at you. But, of course, this doesn’t stop replay attacks.

However, there’s no support for response validation. Even though you can configure a response model for an endpoint, it’s only used for documentation purposes. You have to implement response validation in your application code. Libraries such as middy has support for this through the validator middleware.

Without response validation, it’s quite easy to accidentally return more data than you intend to.

For example, if you record a user’s dob in the User table, should you return it to everyone who views this user’s profile or just the user himself/herself? What about the user’s email?

You might not display these personal data in the UI, but it will still be there in the HTTP response. Anyone with an HTTP proxy can snoop the data.

It’s entirely possible to do a good job of request and response validation with API Gateway. But it’s laborious and API Gateway doesn’t cut you much slack for making mistakes (or simply forgetting to do it).

Now, look at what I need to do in AppSync.

Alt Text

It’s all in the GraphQL type definitions! There is no extra work involved.

What’s not included in the type definition will not be returned. It’s inherent to how GraphQL works. This affords you more time to think about your domain and how best to model it with types, and less time worrying about the mechanics of request and response validation.

3. Scalable WebSockets

Back in re:invent 2018, API Gateway announced support for WebSockets. But once again, it’s anything but easy to use.

You, as the application developer, have to maintain the mapping of the WebSocket connection to users or groups. You do this by implementing Lambda functions that handle the API Gateway onConnect and onDisconnect events. When you need to send messages to specific users, you have to find their connection IDs and call the API Gateway Management API to send messages one at a time.

Alt Text

Again, this is a very low-level construct and puts a lot of the heavy-lifting on your shoulders. And it can be very cost-inefficient for implementing group chats or broadcasts.

Imagine if you’re building a sports streaming app, and you want to notify everyone watching Barcelona vs Real Madrid that a goal has gone in. If you have a million viewers for that match, then that translates:

  • a million reads from your DynamoDB table

  • a million API requests to the API Gateway Management API

That is, if your Lambda function doesn’t timeout before that, and that you don’t get throttled somewhere along the way!

That said, API Gateway’s WebSockets work fine for those simple use cases where you need to send messages to a small group of users at a time. For example, 1–2–1 private chat, or even group chats where you can limit the size of the group.

However, it is still fairly laborious to implement and forces you to spend a lot of time on the mechanics (the “how”) of socket management rather than what messages to send (the “what”).

By comparison, AppSync subscriptions are a breeze to work with.

Alt Text

And since AppSync announced support for pure WebSockets it can now support millions of connected clients. All these, and you don’t have to manage any connections yourself!

That’s not to say that AppSync subscriptions are without its own rough edges. But more on that in another post.

4. Automated API Documentation

We touched on the request and response models earlier. Once you have configured the models for your endpoints, they will be included in the API documentation that you can export from the API Gateway console.

Alt Text

This can also be done through the AWS CLI or the AWS SDK. But like so many other things with API Gateway, it’s just not as easy as you’d like…

What about contract-first development? It’s an increasingly popular practice where teams would start by collaborating and agreeing on the API contracts first before they start implementing the API endpoints.

API Gateway allows you to create an API using an openAPI spec.

Alt Text

However, this flow is not supported by many of the tools we use to build APIs with API Gateway and Lambda — e.g. Serverless framework or SAM.

With GraphQL, contract-first development is kinda a given. It’s innate to how people build GraphQL APIs, by starting with defining the types, and queries and mutations. And the resulting .graphql spec is also self-documenting!

5. Integration with DynamoDB/ElasticSearch/RDS

At the time of writing, AppSync has direct (that is, without proxying through a Lambda function) integration with DynamoDB, Lambda, RDS, ElasticSearch and HTTP.

In terms of the number of direct integration with other AWS services, API Gateway wins hands down. There’s no contest here, as API Gateway can integrate with pretty much every other AWS service.

Alt Text

In both cases, API Gateway and AppSync use Apache VTL as the scripting language for these service integrations.

However, in terms of “how” you integrate with other services, the differences between the two are startling.

Based on my experience working on the serverless-apigateway-service-proxy plugin for the Serverless framework, API Gateway service proxies have multiple conventions. Sometimes it integrates with other AWS services through a signed HTTP request to the service’s REST API. Other times you have to write custom VTL code.

In every case, the configuration is fairly complicated as there are a lot of different things you have to configure. Here is a case in point in case you’re wondering.

With AppSync, you always have to write some VTL, which isn’t everyone’s cup of tea. But fortunately, in practice, it’s mostly a case of filling in your bits in a VTL template. And the official documentation does a good job and provides lots of examples you can just copy and paste into your resolver.

Alt Text

On top of that, the AppSync resolver has built-in support for generating pagination tokens for DynamoDB’s Query and Scan operations. Which obfuscates the internal mechanics of passing the LastEvaluatedKey from the previous query as the ExclusiveStartKey for the next query. This is something that many people get wrong and accidentally couples their client-server communication to implementation details in DynamoDB. You can read more about how to do paginations properly here.

Overall, I find that while AppSync has a limited number of direct service integrations, what it has are much better documented and much easier to work with.

Wrap Up

So that’s it, here are five glorious reasons to consider using AppSync instead of API Gateway for your next project:

  • it supports Cognito group-based authorization natively

  • request and response validation is built into how GraphQL works

  • its WebSockets implementation is both easy to use and highly scalable

  • it works well with contract-first development and doesn’t need an extra process for generating API documentation

  • its service integration with DynamoDB is much easier to use and offers other value-add features such as pagination tokens

In general, I find that everything I have listed here can be achieved with API Gateway. But in every case, it requires significantly more effort than with AppSync.

On a recent client project, I needed to implement Cognito group-based authorization as well as request and response validation reliably for every API endpoint as we were dealing with sensitive personal data.

After toiling and failing for a week, I decided to pivot to AppSync. And within a few hours, I was able to implement everything we needed and more. As a result, I was able to get the project back on track and focus on solving the meat of the business problem at hand.

Simply put, AppSync is to GraphQL what API Gateway is to REST APIs.

And more.

Learn how easy AWS Lambda monitoring can be with Lumigo.


Originally published at https://lumigo.io.

Posted on by:

theburningmonk profile

Yan Cui

@theburningmonk

AWS Serverless Hero. Consultant. Speaker. Trainer. Blogger.

AWS Heroes

This is a collection of articles written by AWS Heroes.

Discussion

markdown guide
 

In your public/private profile example, how would you denote in the GraphQL schema which is shown? Would a query to 'me' show the private one and a query to 'user(id:123)' show the public one? What if the user queries themselves with the second one? Does it still exclude the data?

 

yes, pretty much, for example, getMyProfile would return your Profile (the private one), but getUserProfile(userId: ID!) would return another user's PublicProfile. Even though both are coming from the same row in the DynamoDB, the fact that some fields are not on the schema means the extra data is excluded and not returned in the result.

 

the way I do role based access is not via Cognito, it's a custom build system. I authenticate the end-user via Cognito and then I use roles to match against a role table. If the role doesn't match that API, I return access denied.
It's much easier to implement this and it's secure

 

Nice! But I have one question about proxying from AppSync to DynamoDB, how do you validate the data and return an error response to the client?

 

What sorta validation are you thinking about? In most cases, you can check in the response template whether there's been an error, if so, you can throw a custom error, but even if you don't, AppSync will throw the DynamoDB error (e.g. conditional check failed). This error handling behaviour is different between the two versions of template though, it's something that trips me up from time-to-time too.

 

I was talking about input validation like the middy middleware where you can check for Ajv input. So you can allow for example if the string is valid inside an array. I am curious now how you do this kind of validation with AppSync and Dynamo talking to each other without a lambda. (I never used AppSync and GraphQL)

Ah, I see. For those, you can perform validation in the VTL request template.

 

We have started to move to amplify. Im still learning it.

If Im not wrong all the appsync stuff also applies to amplify

 

Amplify is a set of related tools (there's the amplify-js client-side library, there's the Amplify CLI, and then the Amplify service which lets you host SPAs, etc.).

You can use the Amplify CLI to help you provision a lot of resources, like Cognito pool, an AppSync API, etc. But you can use AppSync completely separate from Amplify. In fact, I don't use the Amplify CLI at all, and instead use the Serverless framework with the serverless-appsync-plugin to configure and provision all the resources.