Skip to content

Instantly share code, notes, and snippets.

@renegoretzka
Last active May 11, 2024 07:17
Show Gist options
  • Save renegoretzka/9ed754c4463b19c883ff8c98492ee5fe to your computer and use it in GitHub Desktop.
Save renegoretzka/9ed754c4463b19c883ff8c98492ee5fe to your computer and use it in GitHub Desktop.
Example to invoke AppSync from AWS Lambda

How to access an AppSync Endpoint from a Lambda function

Setup in project

Run amplify add function and go thru the manual configuration and add access to other resources. Add API here. Run amplify update api if you have a project already setup and configure IAM as an additional auth mode.

Setup in schema.graphql

Add the authorization as following to your type definition:

type Something
  @model
  @auth(
    rules: [
      { allow: private, provider: iam }
    ]
  ) {
  id: ID!
  something: String
}

This allows Lambda to fetch from this type.

Setup in lambda

  • Open the terminal in the amplify/backend/function/(nameOfYourFunction)/src directory in your newly created Lambda function.
  • Run npm install appsync-client graphql-tag

Change the code as you find in this gist's JavaScript file.

When you are done setting up your Lambda function to your desired result run amplify push to push your Lambda function to the AWS Cloud.

exports.getSomething = /* GraphQL */ `
query GetSomething($id: ID!) {
getSomething(id: $id) {
id
someField
}
}
`;
/* Amplify Params - DO NOT EDIT
* Note: This will be auto generated for you by the CLI!
API_APINAME_GRAPHQLAPIENDPOINTOUTPUT
API_APINAME_GRAPHQLAPIIDOUTPUT
ENV
REGION
Amplify Params - DO NOT EDIT */
const AppSyncClient = require('appsync-client').default
const gql = require('graphql-tag')
const config = {
apiUrl: process.env.API_APINAME_GRAPHQLAPIENDPOINTOUTPUT // Change the GraphQL Endpoint to your environment, which you can find in the Amplify Params
}
const client = new AppSyncClient(config)
const { getSomething } = require("./graphql.js"); // graphql.js exports the GraphQL query
const querySomething = async (id) => { // Example function
try {
const result = await client.request({
query: gql(getSomething), // use your graphql query here
variables: {
input: {
id
}
}
})
return result.getSomething;
} catch (error) {
console.log("Something went wrong in the querySomething function:", error)
}
}
exports.handler = async (event) => {
try {
return await querySomething("someRandomId"); // You can also use the arguments / input from the event.
} catch (error) {
console.log("Something went wrong in the handler:", error)
}
}
@flogy
Copy link

flogy commented Sep 24, 2021

Thanks for the example!

I've used HTTPS requests using API Key authentication before, which is not the recommended way for production apps though. So switched to IAM authentication now using your example 👍

Only thing I've done differently is that I used https://www.npmjs.com/package/appsync-client library as it is more lightweight and does not require react to be installed as dependency.

@ptejada
Copy link

ptejada commented Sep 29, 2021

This is a nice guide for sure. I myself was stuck figuring this out at some point. While my original solution did include using aws-appsync I quickly moved away from it because of all the dependencies it pulls. It adds ~10MB to your Lambda functions.

It might not be the easiest to understand but the solution from the official documentation is probably the lightest solution. Mostly because it uses node built-in libraries and the aws-sdk that is available in the Lambda environment so it does not have to be bundled in your function.

My current solution is a variation of the official example. I use a fetch polyfill instead of https directly but honestly, you can probably adapt the example for your favorite HTTP client. Here is my simple function

async function execute<R>(query: string, variables): Promise<QueryResult<R>> {
  const req = new AWS.HttpRequest(new AWS.Endpoint(API_URL), REGION)

  req.method = 'POST'
  req.path = '/graphql'
  req.headers.host = endpoint
  req.headers['Content-Type'] = 'application/json'
  req.body = JSON.stringify({query, variables})

  // @ts-ignore Use of private API
  const signer = new AWS.Signers.V4(req, 'appsync', true)
  // @ts-ignore Use of private utility
  signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate())

  return fetch(API_URL, {
    method: 'POST',
    headers: req.headers,
    body: req.body
  }).then(result => result.json())
}

I did not know packages like appsync-client, aws4 and aws4fetch exist. If I run into problems with my current solution I will probably try one of these libs.

@renegoretzka
Copy link
Author

I updated this gist to a more lightweight version with the appsync-client.
Thank you for your feedback!

@hackrx
Copy link

hackrx commented Dec 8, 2021

Hey @renegoretzka, unfortunately, it is not working for me. Mutation createUser is returning null with no errors.
Here are the steps which I did to setup postConfirmation lambda function: (I want to create a user doc, after the user is confirmed)

  1. Update Graphql default auth mode to cognito and added IAM as additional auth provider.
  2. amplify update function and selected the postConfirmation function.
    Result:
amplify update function                 ✔  39s 
? Select the Lambda function you want to update blog8xxxxx239PostConfirmation
General information
- Name: blog8xxxxx239PostConfirmation
- Runtime: nodejs

Resource access permission
- blog (Query, Mutation)

Scheduled recurring invocation
- Not configured

Lambda layers
- Not configured

Environment variables:
- Not configured

Secrets configuration
- Not configured

? Which setting do you want to update? Resource access permissions
? Select the categories you want this function to have access to. api
? Api has 2 resources in this project. Select the one you would like your Lambda to access blog
? Select the operations you want to permit on blog Query, Mutation

You can access the following resource attributes as environment variables from your Lambda function
        API_BLOG_GRAPHQLAPIENDPOINTOUTPUT
        API_BLOG_GRAPHQLAPIIDOUTPUT
? Do you want to edit the local lambda function now? No

Then amplify push.
Lambda code is the same as yours.
but in config variable, it takes accessID, and secretKey from env. right? but I can't see these keys in the lambda console, only APIENDPOINTOUPUT and APIIDOUTPUT exist.

image

@hackrx
Copy link

hackrx commented Dec 8, 2021

The policy is also attached with PostConfirmation lambda function role, which allows query and mutation.
image

@hackrx
Copy link

hackrx commented Dec 8, 2021

it worked, I just removed input from variables and passed the values directly.
Also, I just read, that we can access secretKey and accessKey in the lambda function, it doesn't show up in env variables.
Thanks, @renegoretzka for the package.

@hackrx
Copy link

hackrx commented Dec 19, 2021

Hey! @renegoretzka, I think this package is not passing errors in the response. For eg.
If I am running this query:

mutation MyMutation( $displayName: String = "", $emailID: String = "", $userName: String = "",  ) {
  createUser(input:  {displayName: $displayName, emailID: $emailID,  userName: $userName}) {
    id
    userName
  }
}

from lambda I am getting :

{ createUser: null }

but in appsync console, with IAM authorization, i am getting this:

{
  "data": {
    "createUser": null
  },
  "errors": [
    {
      "path": [
        "createUser"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 8,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access createUser on type User"
    }
  ]
}

so it should pass errors, in order to throw or handle the case.

@Crusade1337
Copy link

Can i also do an GraphQL Query via AppSync in a lambda that i created via cdk ? Or is it only for amplify lambda functions? I'm still trying to figure out how exactly amplify and cdk can work together..

My use case would be that I have a scheduled lambda that has to perform a graphql query to get data from dynamodb

@charlieforward9
Copy link

Can somebody provide an example for how this is done with the AWS SDK for Javascript v3. The API has changed quite a bit and it is not straightforward on which class I should be using to create the request object.

@djom202
Copy link

djom202 commented May 11, 2024

Hi,

Currently I'm doing the same however I don't use IAM roles, I just use the api-key, that it will be possible to implement?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment