With the release of AWS AppSync we finally have the ability to create realtime serverless apps on AWS. Previously you were forced to spin up EC2 instances in order to create websocket connections as they are not supported by AWS Lambda. In the tutorial you’ll learn how to build a simple API using AWS AppSync. Then you’ll write a client app in vanilla (no frameworks) that receives realtime updates via websockets. Let’s get started! GraphQL javascript 1. Setup Go ahead and install the serverless framework cli which we will be using to deploy our AppSync GraphQL API and create a new directory for our project. $ npm install -g serverless $ mkdir realtime-chat$ cd realtime-chat 2. Create GraphQL Schema We will define a basic GraphQL schema for our chat application. schema {query: Querymutation: Mutationsubscription: Subscription} type Subscription {inbox(to: String): Page (mutations: ["message"])} @aws_subscribe type Mutation {message(body: String!, to: String!): Page!} type Message {from: String!to: String!body: String!sentAt: String!} type Query {me: String} A standard GraphQL schema apart from the realtime subscription which uses a special syntax to indicate to AWS AppSync which mutation to subscribe to ( ), in this case the mutation. @aws_subscribe(mutations: ["message"]) message 3. Create Mapping Templates Now we have our schema defined we need to add resolvers for it. If you’re expecting to need to write a lambda function you’d be wrong! AppSync introduces the concept of which translate the client request to one the backing store (DynamoDB, elasticsearch etc) understands and then translates the response back to the client again. mapping templates To keep things simple we are creating an API without a database. AppSync offers a special type of resolver called a which does not persist the request data but instead just relays it on to whatever subscribers exist at the time. local resolver Let’s create a directory to house our mapping templates. $ mkdir mapping-templates Then let’s create the request template for our mutation in a file called which will extract the fields from the mutation request. message mapping-templates/Message.request.vtl {"version": "2017-02-28","payload": {"body": "${context.arguments.body}","from": "${context.identity.username}","to": "${context.arguments.to}","sentAt": "$util.time.nowISO8601()"}} For the response we just use the standard forwarding template. Create a file called with the following contents. mapping-templates/ForwardResult.response.vtl $util.toJson($context.result) Your folder structure should now look like the following: $ tree mapping-templates mapping-templates ├── ForwardResult.response.vtl └── Message.request.vtl 4. Deploy AppSync GraphQL API with Serverless Framework Now we need to create a config file for the serverless framework to provision our API. In order to do this we’re going to use the . Serverless-AppSync-Plugin Install it with . npm $ npm install --dev serverless-appsync-plugin Then create a file with the following contents. serverless.yml ---service: realtime-chat frameworkVersion: ">=1.21.0 <2.0.0" plugins: serverless-appsync-plugin provider:name: awsregion: eu-west-1 custom:awsAccountId: ${env:AWS_ACCOUNT_ID}appSync:name: realtimeChatapiKey: ${env:APPSYNC_API_KEY}apiId: ${env:APPSYNC_API_ID}authenticationType: API_KEYschema: schema/schema.graphqlserviceRole: "AppSyncServiceRole" # AppSyncServiceRole is a role defined by amazon and available in all accountsmappingTemplatesLocation: mapping-templatesmappingTemplates:- dataSource: Chattype: Mutationfield: messagerequest: Message.request.vtlresponse: ForwardResult.response.vtl- dataSource: Chattype: Subscriptionfield: inboxrequest: Message.request.vtlresponse: ForwardResult.response.vtldataSources:- type: NONE # use an AppSync local resolvername: Chatdescription: 'Chat relay' As you can see we set the data source type to in order to use the as we do not want to persist the chat messages in a database but instead just forward them to other clients listening for updates. NONE local resolver Our config contains a few environment variables that we must supply. Let’s create a file that contains our AWS Account ID and dynamically populates the other variables. serverless.yml .env # .env export AWS_ACCOUNT_ID=123456789 export APPSYNC_API_ID=$(aws appsync list-graphql-apis \--query 'graphqlApis[?name==`realtimeChat`].apiId' \--output text >/dev/null 2>&1) export APPSYNC_API_KEY=$(aws appsync list-api-keys \--api-id "$APPSYNC_API_ID" \--query 'apiKeys[0].id' \--output text >/dev/null 2>&1) Now we’re ready to deploy our API with a single command: Congratulations! You’ve just deployed a GraphQL API with realtime support. 5. Create GraphQL Queries For Client Application The next thing we need to do is to create the GraphQL queries that we will be using from our client to query our API. First let’s create a directory to house our client code. $ mkdir src Then let’s create a directory for our queries. $ mkdir src/graphql Create a file at for our subscription query with the following contents: src/graphql/inboxSubscription.js import gql from 'graphql-tag'; export default gql`subscription Inbox($to: String) {inbox(to: $to) {frombody}}`; This is just a simple subscription query which will return and message fields. from body 6. Download API Config from AWS Console We need to download the config settings for the app so that it can connect to our GraphQL API. Navigate to AppSync section in the AWS Console. Select your API and download the web config settings. You will have a file named in your download area. Move this your directory and rename it to . AppSync.js src config.js The config file you download should look like the following if you have left the default option for securing your API as . API_KEY export default {"graphqlEndpoint": " ","region": "eu-west-1","authenticationType": "API_KEY","apiKey": "xxxxxxxxxxxxxxxxxxxxxxxxx"} https://xxxx.appsync-api.eu-west-1.amazonaws.com/graphql 7. Isomorphic Vanilla JavaScript Client Code For Subscribing to Realtime API Updates We are going to create an isomorphic client — one that can be run either in the browser or via node.js in a terminal. First let’s install the dependencies we’ll need. $ npm install -s apollo-cache-inmemory apollo-client apollo-link aws-appsync aws-sdk es6-promise graphql graphql-cli graphql-tag isomorphic-fetch ws Then let’s create an entrypoint for the application. Your app source code directory should now have the following contents. $ tree srcsrc├── config.js├── graphql│ └── inboxSubscription.js└── index.js Paste the following code into your file: index.js const RECIPIENT = 'Bobby'; if (!global.WebSocket) {global.WebSocket = require('ws');}if (!global.window) {global.window = {setTimeout: setTimeout,clearTimeout: clearTimeout,WebSocket: global.WebSocket,ArrayBuffer: global.ArrayBuffer,addEventListener: function () { },navigator: { onLine: true }};}if (!global.localStorage) {global.localStorage = {store: {},getItem: function (key) {return this.store[key];},setItem: function (key, value) {this.store[key] = value;},removeItem: function (key) {delete this.store[key];}};}require('es6-promise').polyfill();require('isomorphic-fetch'); // Require config file downloaded from AWS console with endpoint and auth infoconst AppSyncConfig = require('./config').default;const AWSAppSyncClient = require('aws-appsync').default;import InboxSubscription from './graphql/inboxSubscription'; // Set up Apollo clientconst client = new AWSAppSyncClient({url: AppSyncConfig.graphqlEndpoint,region: AppSyncConfig.region,auth: {type: AppSyncConfig.authenticationType,apiKey: AppSyncConfig.apiKey,}}); client.hydrated().then(function (client) { const observable = client.subscribe({ query: InboxSubscription, variables: { to: RECIPIENT } }); const realtimeResults = function realtimeResults(data) {console.log('realtime data: ', data);}; observable.subscribe({next: realtimeResults,complete: console.log,error: console.log,});}); For this simple demo we have hardcoded the recipient to but obviously you’d want to make this dynamic for a real application. Bobby 8. Node.js Client Application Build Process At this point we have all the source code written for our websockets client, we just need to implement the build process. Because the source code uses es6 features we need to transpile it using babel. Install the dev dependencies we’ll need. $ npm install --save-dev babel-cli babel-preset-es2015 rimraf webpack webpack-cli webpack-dev-server Now let’s build our application. $ rimraf build/ && babel ./src --out-dir build/ --ignore ./node_modules,./.babelrc,./package.json,./npm-debug.log --copy-file 9. Node.js Client Application Now let’s run our application in node.js. $ node build/index.js Navigate to the page in your AppSync API in the AWS console and run the following graphQL mutation to trigger some udpates. queries mutation Message {message(to: "Bobby", body: "Yo node!") {bodytofromsentAt}} You should see the message appear immediately in your terminal. 10. Browser Client Application Build Process Remember the code we’ve written is isomorphic. That means it’ll run just as well in the browser. First install the dev dependencies we’ll need. $ npm install --save-dev webpack webpack-cli webpack-dev-server We’re going to use to run our build process so we need to create a config file for it. Create a file called at the route of the project with the following contents. webpack webpack.config.js const path = require('path'); module.exports = {entry: './src/index.js',output: {filename: 'bundle.js',path: path.resolve(__dirname, 'dist')}}; As the webserver will be serving assets from the directory we need to add an file that includes the file which webpack will generate. Create a file at with the following contents. dist index.html bundle.js dist/index.html <!DOCTYPE html><html><head><title>AWS Serverless Websockets Demo</title></head><body> </body><script type="text/javascript" src="bundle.js"></script></html> 10. Browser Client Application Run the following command which will start serving assets from the directory. [webpack-dev-server](https://github.com/webpack/webpack-dev-server) dist $ webpack-dev-server --mode development --content-base dist/ Then navigate to in your browser and open the dev tools as we’ll be logging the data to the console. http://localhost:8080 Navigate to the page in your AppSync API in the AWS console and run another graphQL mutation to trigger an update. queries mutation Message {message(to: "Bobby", body: "hello browser!") {bodytofromsentAt}} You should see the updates being logged to your browser’s console. 12. Conclusion We’ve set up a serverless GraphQL API with node.js and browser clients consuming realtime updates via websockets. Not bad for under 30 minutes work! Although AppSync is promoted as a managed GraphQL API service, for me its best feature is the ability to serve realtime updates. Previously you would have had to run a server 24/7 to do this. Now you get all the cost savings of serverless and without any of the headaches of managing servers. Full source code for this available on github. serverless websockets example If you’re interested in learning more about building realtime serverless applications then check out my upcoming training course . Full Stack Serverless GraphQL Apps on AWS AppSync Originally published at andrewgriffithsonline.com .