Code Monkey home page Code Monkey logo

graphql's Introduction

Serverless GraphQL Component

This Serverless Framework Component is a specialized developer experience focused on making it easy to deploy and manage GraphQL applications on serverless infrastructure (specifically AWS AppSync and AWS Lambda) on your own AWS account. It comes loaded with powerful development features and represents possibly the easiest, cheapest and most scalable way to host GraphQL apps.


  • Never Pay For Idle - No requests, no cost. Averages $0.0000002-$0.0000009 per request.
  • Zero Configuration - All we need is your code, then just deploy (advanced config options are available).
  • Fast Deployments - Deploy to the cloud in seconds.
  • Realtime Logging - Rapidly develop on the cloud w/ real-time logs and errors in the CLI.
  • Team Collaboration - Collaborate with your teammates with shared state and outputs.
  • Custom Domain + SSL - Auto-configure a custom domain w/ a free AWS ACM SSL certificate.
  • Lambda Default Resolver - Automatically deploys your code to a lambda function for rapid query resolution.
  • Works with All Data Sources - Can be configured to work with directly with DynamodDB, and other data sources.
  • Flexible Authorization Options - Supports all AppSync authorization options, API Key, IAM, Cognito or OpenID auth.

Contents

Quick Start

Install

To get started with this component, install the latest version of the Serverless Framework:

npm install -g serverless

After installation, make sure you connect your AWS account by setting a provider in the org setting page on the Serverless Dashboard.

Initialize

The easiest way to start using the graphql component is by initializing the graphql-starter template. Just run this command:

serverless init graphql-starter
cd graphql-starter

This will also run npm install for you. You should now have a directory that looks something like this:

|- serverless.yml
|- schema.graphql
|- resolvers.js

The serverless.yml file is where you define your component config. It looks something like this:

component: graphql
name: graphql-api

inputs:
  src: ./

For more configuration options for the serverless.yml file, check out the Configuration section below.

The schema.graphql is where you define your GraphQL schema. It looks something like this:

type Post {
  id: ID!
}

type Query {
  getPost(id: ID!): Post
}

type Mutation {
  createPost(id: ID!): Post
}

schema {
  query: Query
  mutation: Mutation
}

The resolvers.js file is where you define your schema resolvers. It looks something like this:

const Query = {
  // resolver for field getPost in type Query
  getPost: async ({ id }) => {
    return { id }
  }
}

const Mutation = {
  // resolver for field createPost in type Mutation
  createPost: async ({ id }) => {
    return { id }
  }
}

module.exports = { Query, Mutation }

In this file, you simply export each of your schema types (ie. Query & Mutation) as an object of functions. Each function is a field resolver for that type.

All these files are required. Needless to say, any resolver you define in resolvers.js, must also be defined in your schema in the schema.graphql file, otherwise, you'll get an AppSync error. Same goes for the resolvers inputs & outputs. Remember, GraphQL is strongly typed by design.

Deploy

Once you have the directory set up, you're now ready to deploy. Just run the following command from within the directory containing the serverless.yml file:

serverless deploy

Your first deployment might take a little while, but subsequent deployment would just take few seconds.

After deployment is done, you should see your the following outputs:

name:   graphql-api-pxzaf135
apiKey: da2-yf444kxlhjerxl376jxyafb2rq
apiId:  survbmoad5ewtnm3e3cd7qys4q
url:    https://cnbfx5zutbe4fkrtsldsrunbuu.appsync-api.us-east-1.amazonaws.com/graphql

Your GraphQL API is now deployed! Next time you deploy, if you'd like to know what's happening under the hood and see realtime logs, you can pass the --debug flag:

serverless deploy --debug

Query

You can query and test your newly created GraphQL API directly with the AWS AppSync console, or any HTTP client.

Here's a snippet using fetch or node-fetch with the example above:

// you can get the url and apiKey values from the deployment outputs
const url = 'https://cnbfx5zutbe4fkrtsldsrunbuu.appsync-api.us-east-1.amazonaws.com/graphql'
const apiKey = 'da2-yf444kxlhjerxl376jxyafb2rq'

fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': apiKey // the "x-api-key" header is required by AppSync
  },
  body: JSON.stringify({
    query: `query getPost { getPost(id: "123") { id }}`
  })
})
  .then((res) => res.json())
  .then((post) => console.log(post))

The response should be an echo of the post id, something like this:

{
  "data": {
    "getPost": {
      "id": "123"
    }
  }
}

Configuration Reference

The GraphQL component is a zero configuration component, meaning that it'll work out of the box with no configuration and sane defaults. With that said, there are still a lot of optional configuration that you can specify.

Here's a very minimal configuration to get you started. Most of these properties are optional, but if you use them, remember to substitute with your own value if required (ie. the org property)

component: graphql               # (required) name of the component. In that case, it's graphql.
name: graphql-api                # (required) name of your graphql component instance.
org: serverlessinc               # (optional) serverless dashboard org. default is the first org you created during signup.
app: myApp                       # (optional) serverless dashboard app. default is the same as the name property.
stage: dev                       # (optional) serverless dashboard stage. default is dev.

inputs:
  src: ./                        # (optional) path to the source folder. default is a simple blogging app.
  region: us-east-2              # (optional) aws region to deploy to. default is us-east-1.

Even the src input is optional. If you didn't specify any src directory containing your code, an example app will be deployed for you.

Keep reading to learn more about all the configuration options available to you.

Extend Existing API

If the appId input variable is provided this component will extend an existing AppSync API:

inputs:
  src: ./
  apiId: xxx                     # (optional) if provided will extend an existing api.

The apiId can be reference from the source component using the apiId output variable from the component instance that created the graphql API: ${output:[STAGE]:[APP]:[NAME].apiId}

Lambda Configuration

If you specify resolvers in a resolvers.js file as shown in the quick start above, the component will deploy a lambda function automatically for you to host your resolvers and connect everything together. You can configure this default lambda function with the following inputs:

inputs:
  src: ./
  description: My GraphQL App    # (optional) lambda description. default is en empty string.
  memory: 512                    # (optional) lambda memory size. default is 3008.
  timeout: 10                    # (optional) lambda timeout. default is 300.
  env:                           # (optional) env vars. default is an empty object
    TABLE: 'my-table'
  layers:                        # (optional) list of lambda layer arns to attach to your lambda function.
    - arn:aws:first:layer
    - arn:aws:second:layer
  vpcConfig:                     # (optional) specify a vpc
    securityGroupIds:
      - sg-xxx
    subnetIds:
      - subnet-xxx
      - subnet-xxx

Custom Domain

If you've purchased your domain from AWS Route53, you can configure the domain with a single input:

inputs:
  src: ./
  domain: example.com

Subdomains work too:

inputs:
  src: ./
  domain: api.example.com

This will create a a free SSL certificate for you with AWS ACM, deploy a CDN with AWS CloudFront, and setup all the DNS records required.

If you've purchased your domain elsewhere, you'll have to manually create a Route53 hosted zone for your domain, and point to the AWS nameservers on your registrar before you add the domain input.

Custom IAM Policies

The component creates the minimum required IAM policy based on your configuration. But you could always add your own policy statements using the policy input:

inputs:
  src: ./src
  policy:
    - Action: '*'
      Effect: Allow
      Resource: '*'

This policy applies to both the built-in Lambda function and the AppSync API. Keep in mind that this component automatically adds the required IAM policies to invoke your data source depending on your configuration.

Authorization

This component uses apiKey authorization by default. However all other AppSync authorization options are available via the auth input.

IAM authorization:

inputs:
  src: ./
  auth: iam

Cognito authorization:

inputs:
  src: ./
  auth:
    userPoolId: qwertyuiop
    defaultAction: ALLOW
    region: us-east-1
    appIdClientRegex: qwertyuiop

OpenID authorization:

inputs:
  src: ./
  auth:
    issuer: qwertyuiop
    authTTL: 0
    clientId: wertyuiop
    iatTTL: 0

Data Sources Resolvers

If you'd like to setup your resolvers to use your own existing data sources, you could specify your resolvers as a serverless.yml input instead of inside a resolvers.js file.

In that case, you'll need to also specify your own request and response VTL templates. You could do that directly in serverless.yml, or by pointing to a vtl file inside of your src directory.

Here's an example using an existing lambda as a data source:

inputs:
  src: ./
  resolvers:
    Query:                          # this must be a valid type in your schema
      getPost:                      # this must be a valid resolver in your schmea
        lambda: my-lambda           # this will set up the my-lambda Lambda as a data source for this resolver
        request: >                  # the request VTL template for this resolver.
          { "version": "2017-02-28", "operation": "Invoke", "payload": $util.toJson($context)  }
        response: response.vtl      # you could also point to a VTL file relative to your src directory.

These request and response properties are required regardless of which data source you are working with, and they're different depending on your schema and your application requirements. Check out the official AWS docs for more information on the required syntax for each data source.

Lambda Resolvers

inputs:
  src: ./
  resolvers:
    Query:                          
      getPost:                      
        lambda: my-lambda
        request: '{ "version": "2017-02-28", "operation": "Invoke", "payload": $util.toJson($context)  }'
        response: '$util.toJson($context.result)'

DynamoDB Resolvers

inputs:
  src: ./
  resolvers:
    Query:                          
      getPost:                      
        table: my-table
        request: >
          {
              "version" : "2017-02-28",
              "operation" : "PutItem",
              "key" : {
                  "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
              }
          }
        response: '$util.toJson($context.result)'

ElasticSearch Resolvers

inputs:
  src: ./
  resolvers:
    Query: 
      getPost:
        endpoint: https://search-my-sample-data-abbaabba.us-east-1.es.amazonaws.com
        request: >
          {
              "version":"2017-02-28",
              "operation":"GET",
              "path":"/id/post/_search",
              "params":{
                  "headers":{},
                  "queryString":{},
                  "body":{
                      "from":0,
                      "size":50
                  }
              }
          }
        response: >
          [
              #foreach($entry in $context.result.hits.hits)
              #if( $velocityCount > 1 ) , #end
              $utils.toJson($entry.get("_source"))
              #end
          ]

Relational Database Resolvers

inputs:
  src: ./
  resolvers:
    Query:    
      getPost: 
        database: my-database
        dbClusterIdentifier: arn:aws:rds:us-east-1:123456789123:cluster:my-serverless-aurora-postgres-1
        awsSecretStoreArn: arn:aws:secretsmanager:us-east-1:123456789123:secret:rds-db-credentials/cluster-ABCDEFGHI/admin-aBc1e2
        relationalDatabaseSourceType: RDS_HTTP_ENDPOINT
        schema: public
        request: >
          {
              "version": "2018-05-29",
                  "statements": [
                      $util.toJson("select * from Posts WHERE id='$ctx.args.id'")
              ]
          }
        response: '$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])'

CLI Reference

deploy

To deploy, simply run deploy from within the directory containing the serverless.yml file:

serverless deploy

If you'd like to know what's happening under the hood and see realtime logs, you can pass the --debug flag:

serverless deploy --debug

dev (dev mode)

Instead of having to run serverless deploy everytime you make changes you wanna test, you can enable dev mode, which allows the CLI to watch for changes in your source directory as you develop, and deploy instantly on save.

To enable dev mode, simply run the following command from within the directory containing the serverless.yml file:

serverless dev

Dev mode also enables live streaming logs from your GraphQL app so that you can see the results of your code changes right away on the CLI as they happen.

info

Anytime you need to know more about your running GraphQL instance, you can run the following command to view the most critical info:

serverless info

This is especially helpful when you want to know the outputs of your instances so that you can reference them in another instance. It also shows you the status of your instance, when it was last deployed, how many times it was deployed, and the error message & stack if the latest deployment failed.

To dig even deeper, you can pass the --debug flag to view the state object of your component instance:

serverless info --debug

remove

If you wanna tear down your entire GraphQL infrastructure that was created during deployment, just run the following command in the directory containing the serverless.yml file:

serverless remove

The GraphQL component will then use all the data it needs from the built-in state storage system to delete only the relavent cloud resources that it created.

Just like deployment, you could also specify a --debug flag for realtime logs from the GraphQL component running in the cloud:

serverless remove --debug

Outputs Reference

name:   graphql-api-pxzaf135
apiKey: da2-yf444kxlhjerxl376jxyafb2rq
apiId:  survbmoad5ewtnm3e3cd7qys4q
url:    https://cnbfx5zutbe4fkrtsldsrunbuu.appsync-api.us-east-1.amazonaws.com/graphql

FAQs

How do I add NPM packages to the resolvers?

You can run npm init & npm install as you normally would in the directory containing the resolvers.js file. This is the root of your app. This entire directory is uploaded to your Lambda function, and you can structure it however you want. Just make sure resolvers.js and schema.graphql are in the root of the directory.

graphql's People

Contributors

brunoarueira avatar eahefnawy avatar hypexr avatar skierkowski avatar wtchnm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

graphql's Issues

[feature request] GraphQL Schema stitching

In order for this Serverless component to be usable for anything more than a hobbyist project, it should support schema stitching out of the box.

As GraphQL projects and codebases grow, schema management becomes critical. The best way to manage a schema at scale is to split it up into multiple smaller files and then "stitch" them during the build/deploy process.

So in order to just specifying a single schema.graphql file, it would be awesome if we could specify either (a) an array/list of files OR (b) a directory.

This feature is also supported by the serverless-appsync-plugin plugin.

ConcurrentModificationException error on deploy

I've inherited a codebase using this component, and am getting the error below when attempting to deploy. Obviously the error is remote, but I can't see why I would be having this issue deploying when the previous dev did not.

There's one issue here (serverless/serverless-graphql#316) which mentions it happening intermittently, but I'm getting it for every attempt (unless I get a timeout which is also happening).

% nvm exec 14.15.4 sls deploy --debug --stage=dev  

Running node v14.15.4 (npm v6.14.10)
Initializing...
Action: "deploy" - Stage: "dev" - Org: "OrgName" - App: "appname-graphql" - Name: "appname-graphql"
Deploying...
Deploying "appname-graphql-n7lr7nr" to the "us-east-1" region.
Deploying Role "appname-graphql-n7lr7nr" to the "us-east-1" region.
Deploying resolvers for AppSync API with ID "yeaexzl3gjeqtak3fftwqu5btm".

 ConcurrentModificationException: Data source locked due to concurrent update, please retry
    at Object.extractError (/var/task/node_modules/aws-sdk/lib/protocol/json.js:52:27)
    at Request.extractError (/var/task/node_modules/aws-sdk/lib/protocol/rest_json.js:55:8)
    at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:688:14)
    at Request.transition (/var/task/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/var/task/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /var/task/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:690:12)

14s › Serverless › Data source locked due to concurrent update, please retry 

  Documentation: https://github.com/serverless/components 
  Support: https://app.serverless.com/support 
  Slack: https://www.serverless.com/slack/ 

The only thing of note is that n7lr7n in appname-graphql-n7lr7n is different from the existing dev stage deployment, possibly because the OrgName has changed. If there's any hints on how to override this so I can get the original hash, that might indirectly solve the problem (in the hope its only happening when doing a fresh deploy).

I tried modifying the apiId: which seems to work for resolvers line, but doesn't change the general naming.

Resolver lambda missing AWSLambdaBasicExecutionRole role and unable to write to cloudwatch logs

The log group exists for the lambda in cloudwatch, but the AWS lambda console shows:

Missing permissions
Your function doesn't have permission to write to Amazon CloudWatch Logs. To view logs, add the AWSLambdaBasicExecutionRole managed policy to its execution role. Open the IAM console

Is this something that is meant to be explicitly added to the lambda's configuration? If so how do go about that?

Thanks

Incorrect account ids in lambda policy

graphql 3.0.3

I have graphql components configured to deploy to different stages each with a different provider defined in the serverless web ui with each provider being a different aws account.

When I deploy to the different stages the resources are created in the correct account, but about 50% of the time the account id in the lambda's role for invokeFunction is incorrect. When this happens making a graphql call returns an error that appsync is not able to call the lambda.

When the account id is incorrect it is an account id from one of the other 2 accounts.

        {
            "Effect": "Allow",
            "Action": [
                "lambda:invokeFunction"
            ],
            "Resource": "arn:aws:lambda:us-west-2:<wrong account id>:function:api-v8-stagename-appname-s2229lk*"
        }

When deploying to one account with one configured provider in the serverless UI it successfully created lambda invokeFunction policies with the correct account ids.

custom domain + region doesn't work together

When I set domain and region (to value other than us-east-1) together in my serverless.yml, I got the following error message when trying to deploy:

InvalidViewerCertificate: The specified SSL certificate doesn't exist, isn't in us-east-1 region, isn't valid, or doesn't include a valid certificate chain.

I have the domain all setup in Route53, and when I remove the region setting, it works fine, so that eliminates the possibility of mis-configed domains. But the resource is deployed to us-east-1, which is not what I wanted.

According to aws doc here

The certificate must be imported in the US East (N. Virginia) Region.

With my limited knowledge of AWS and serverless framework, my theory is that the region config is used by the process to import the certificate. I think we should hard code it to use us-east-1 right now given it's a hard requirement from AWS officially.

Customizing of the app-sync name

It seems that the name of the instance as specified in the serverless.yml is also used as the name withing app sync, plus some unique id. It would be preferable if that could be overwritten, in our case I would prefer the name to include the stage to make identification easier.

E.g. issuer becomes issuer-prod-234drfs3 when deployed for production.

No way to write initialization and finalization code with built-in resolvers.js

It's common practice to write initialization process like db connection (opening db connection and reuse it in handler function) at the top of handler function and write finalization process like db closing (closing db connection before lambda container would be destroyed).
How could we do it with resolvers.js because this resolvers.js wouldn't include handler function definition?

Add option to enable cloudwatch logging

It seems that by default the lambda deployed by graphql has it's logs captured and sent to serverless and they don't show up in the cloudwatch console.

It would be ideal if there was a way to configure it to also log to cloudwatch.

OpenId Auth - BadRequestException: Authentication type not specified

Hi,
When specifying the the auth option for OpenID, I get an error saying:

"BadRequestException: Authentication type not specified"
I believe this is an issue here:
https://github.com/serverless/aws-sdk/blob/b552a3954e6cadb093db4127cb8458192c7bb35b/src/deployAppSyncApi.js

But it could be an issue with how the structure of the yaml in this component.

I don't know if this is something that is not supported yet, a bug, or my bad implementation.

Any help would be appreciated.

For clarity my serverless.yaml looks like this (with placeholders replaced with real value):

component: graphql
name: [my app]-graphql

inputs:
  src: ./build
  auth:
    issuer: https://[domain].auth0.com
    authTTL: 0
    clientId: [my client id]
    iatTTL: 0


Shouldn't require/execute resolvers.js when deploying the built in resolvers lambda

Unexpected errors can occur when resolvers.js is loaded as part of the deploy process.

For example, it's common to create things like DB connections outside of the handler so that they persist as long as the lambda is running without being recreated on each call. Another thing that I'm experiencing is that I instrument my lambdas with aws xray and that loads and instruments http which can errors.

If this require was replaced with a fs read file and it was parsed differently it would still be possible to determine the keys under Query and Mutation.

Add vpcConfig for built in lambda resolver

Currently the config sets these lambda parameters:

const deployLambdaParams = {
        lambdaName: this.state.name,
        description: inputs.description,
        handler,
        memory: inputs.memory,
        timeout: inputs.timeout,
        env: inputs.env,
        layers: inputs.layers,
        roleArn,
        lambdaSrc: zipPath
      }

vpcConfig needs to be added. It would be a nice distinction if the lambdas parameters were broken out from the top level inputs into their own key.

Can you specify both cognito and IAM?

A standard pattern when using subscriptions to database updates is to use IAM authentication for certain mutations (example directive: @aws_iam).

Can you specify multiple authentication types using this component?

Missing http resolver

It doesn't look like there's support for HTTP resolvers. When I specify an endpoint it assumes it's the elasticsearch resolver. Are there plans to add it?

Intermittent deploy failure: TypeError: self.intercepts.console[type].apply is not a function

Hello, we've seen some intermittent deployment failures when deploying graphql components. I put this in with support and they asked me to put it in here.

Here's the output:

25lInitializing...
Action: "deploy" - Stage: "dev" - Org: "rioverde" - App: "api" - Name: "base"
Deploying...
Deploying "api-v8-dev-nw3gq5z" to the "us-west-2" region.
TypeError: self.intercepts.console[type].apply is not a function
at intercept (/opt/nodejs/node_modules/@serverless/core/node_modules/@serverless/platform-client/src/index.js:1289:41)
at console.log (/opt/nodejs/node_modules/@serverless/core/node_modules/@serverless/platform-client/src/index.js:1298:7)
at log (/var/task/serverless.js:28:30)
at GraphQL.deploy (/var/task/serverless.js:95:5)
at async Runtime.module.exports [as handler] (/opt/nodejs/node_modules/@serverless/core/handler.js:337:24)
6s › Serverless › self.intercepts.console[type].apply is not a function
Documentation: https://github.com/serverless/components
Support: https://app.serverless.com/support
Slack: https://www.serverless.com/slack/
25hError executing "AWS_PROFILE=rio-beta sls deploy --stage dev --debug" Exit code: 1
Error: Process completed with exit code 1```

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.