Dr.YSG
Dr.YSG

Reputation: 7591

Debug Apollo Server as AWS Lambda Function

I followed the directions at Apollo Server for deploying as AWS lambda. https://www.apollographql.com/docs/apollo-server/deployment/lambda/ used the serverless framework and it is working fine in region east-2.

I extended the example to use a PostGres DB for queries (I used the npm sequalize package). The same code is used fine when I run as an ApolloServer and a local postresql DB. I made it so that it can also switch to an apollo-server-lambda. I have an if statement that changes the connection to the DB depending on if it is a lambda or not.

The issue I have is that queries that do not hit the DB work fine. But graphql queries to the DB return:

{
  "error": {
    "message": "Internal server error"
  }
}

OK, So now how do I debug a nodejs lambda function?

The lambda management console does have a test operation. I redefiend the hello world test to use this as the test:

{
  "operationName": null,
  "variables": {},
  "query": "{users {id firstName lastName addressNumber streetName city email createdAt updatedAt }}"
}

But that does not seem to be the right way to invoke the lambda function. Because the log returns:

{
  "body": "Apollo Server supports only GET/POST requests.",
  "statusCode": 405,
  "headers": {
    "Allow": "GET, POST"
  }
}


server.js

const { ApolloServer } = require('apollo-server')
const { ApolloServer: ApolloServerLambda } = require('apollo-server-lambda')
const { typeDefs, resolvers, connect } = require('./schema.js')

// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.

async function setup(server) {
  let { url } = await server.listen()
  console.log(`🚀  Server ready at ${url}`)
  await connect("local")
}

async function awsSetup() {
  await connect("aws")
}

if (process.env.USERNAME == 'ysg4206') {
  const server = new ApolloServer({ typeDefs, resolvers })
  setup(server)
} else {
  const server = new ApolloServerLambda({ typeDefs, resolvers })
  //awsSetup()
  exports.graphqlHandler = server.createHandler({
    playground: true,
    introspection: true,
    cors: {
      origin: '*',
      credentials: true,
    },
    context: ({ event, context }) => { return (
      {
        headers: event.headers,
        functionName: context.functionName,
        event,
        context
      })
    }
  })
}

schema.js

const { gql } = require('apollo-server')
const { DB } = require('./db')
const { GraphQLDateTime } = require('graphql-iso-date')

exports.typeDefs = gql`
  scalar DateTime

  type User {
    id: Int
    "English First Name"
    firstName: String
    lastName: String
    addressNumber: Int
    streetName: String
    city: String
    email: String
    createdAt: DateTime
    updatedAt: DateTime
  }

  input UserType {
    "Hebrew First Name"
    firstName: String
    lastName: String
    addressNumber: Int
    streetName: String
    city: String
    email: String
  }

  type Query {
    users: [User]
    findUser(firstName: String): User
    hello(reply: String): String
  }

  type Mutation {
    addUser(user: UserType): User!
  }

  type Subscription {
    newUser: User!
  }
`

exports.resolvers = {
  Query: {
    // users: async () => {
    //   let users = await DB.findAll()
    //   return users
    // },
    users: () => DB.findAll(),
    findUser: async (_, { firstName }) => {
      let who = await DB.findFirst(firstName)
      return who
    },
    hello: (_, { reply }, context) => {
      console.log(`hello with reply ${reply}`)
      console.log(`context : ${JSON.stringify(reply, null, 4)}`)
      return reply
    }
  },
  Mutation: {
    addUser: async (_, args) => {
      let who = await DB.addUser(args.user)
      return who
    }
  }
}

exports.connect = async function connect(where) {
  await DB.dbSetup(where)
  await DB.populate()
  let users = await DB.findAll()
  console.log(users)
}


Upvotes: 3

Views: 2775

Answers (2)

radihuq
radihuq

Reputation: 1082

For anyone deploying their serverless app and receiving {"message": "Internal server error"} when going to their AWS endpoint

I was running into this issue for a whole day. I made a couple of changes but I think what did it for me was including context when initializing my ApolloServer:

const server = new ApolloServer({
    ...serverConfig, // typeDefs and resolvers
    context: ({ event, context }) => ({
        headers: event.headers,
        functionName: context.functionName,
        event,
        context,
    }),
    playground: {
        endpoint: '/dev/graphql',
    },
});

See this link: https://www.apollographql.com/docs/apollo-server/deployment/lambda/#getting-request-info

When debugging I recommend two things

  1. Check your lambda function's Cloudwatch logs on AWS
  2. Run serverless offline, go to the local endpoint and see if you get any errors (you will need to install the serverless offline plugin and include it in your serverless.yml)

Also some related additional information if you're using a yarn monorepo and typescript -

Make sure to compile and transpile the typescript code. See this article. My code:

  • yarn add webpack serverless-webpack
  • yarn add -D webpack-node-externals

webpack.config.js

const path = require('path');
const slsw = require('serverless-webpack');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    entry: slsw.lib.entries,
    target: 'node',
    mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
    optimization: {
        minimize: false,
    },
    performance: {
        hints: false,
    },
    devtool: 'nosources-source-map',
    externals: [nodeExternals()],
    module: {
        rules: [
            {
                test: /\.ts$/,
                loader: 'babel-loader',
                options: {
                    presets: [
                        [
                            '@babel/preset-env',
                            {
                                targets: {
                                    node: true,
                                },
                            },
                        ],
                        '@babel/typescript',
                    ],
                },
                include: [__dirname],
                exclude: /node_modules/,
            },
        ],
    },
    resolve: {
        extensions: ['.ts', '.js'],
    },
    output: {
        libraryTarget: 'commonjs2',
        path: path.join(__dirname, '.webpack'),
        filename: '[name].js',
    },
};

serverless.yml

# serverless.yml
service: apollo-lambda
plugins:
    - serverless-webpack
    - serverless-offline
custom:
    webpack:
        webpackConfig: ./webpack.config.js
        includeModules: true
provider:
    name: aws
    runtime: nodejs12.x
functions:
    graphql:
        # this is formatted as <FILENAME>.<HANDLER>
        handler: dist/server.graphqlHandler
        environment:
            SLS_DEBUG: true
        events:
            - http:
                  path: graphql
                  method: post
                  cors: true
                  integration: lambda-proxy
            - http:
                  path: graphql
                  method: get
                  cors: true
                  integration: lambda-proxy

tsconfig.json

{
    "compilerOptions": {
        "sourceMap": true,
        "outDir": "./dist",
        "strict": true,
        "lib": ["es5"],
        "esModuleInterop": true,
        "types": ["react", "jest"]
    }
}

I also added a script called deploy which deletes the dist folder, recreates the dist folder after compiling the typescript (see outDir in tsconfig.json), and then runs serverless deploy:

package.json

{
  ...
  "main": "dist/server.js",
  "scripts": {
    ...
    "deploy": "rimraf dist && npx tsc && serverless deploy",
  },
  ...
}

Note: you'll need to install rimraf globally for the script to work (npm install -g rimraf)

Upvotes: 2

Aaron Stuyvenberg
Aaron Stuyvenberg

Reputation: 3777

Debugging lambda functions is hard! You can try digging through logs if they are configured through CloudWatch, but that won't always give you a workable stack trace, and it's challenging to find the exact invocation you're looking for.

Have you tried deploying with the Serverless Framework Dashboard? That will help give you a full stack trace along with your logs. You can get started by simply running the serverless command in your application root directory.

More information is available here

Full disclosure - I work for Serverless Inc. on the Serverless Framework.

Upvotes: 1

Related Questions