Error: Unable to determine event source based on event. (apollo-server-lambda)

I have this issue after updating to apollo-server-lambda@3^

“Error: Unable to determine event source based on event.”

This code used to work a while ago but since the version 3^ of apollo-server-lambda my code is breaking and gives me the error message above…

This is my netlify back-end function code: (graphql.js)

const {
  ApolloServerPluginLandingPageGraphQLPlayground,
  // ApolloServerPluginLandingPageDisabled,
} = require('apollo-server-core');
const faunadb = require('faunadb');

const q = faunadb.query;

const client = new faunadb.Client({ secret: process.env.GATSBY_FAUNADB });

const typeDefs = gql`
  type Query {
    todos: [Todo]!
  }
  type Todo {
    id: ID!
    text: String!
    done: Boolean!
  }
  type Mutation {
    addTodo(text: String!): Todo
    updateTodoDone(id: ID!): Todo
  }
`;

const resolvers = {
  Query: {
    todos: async (parents, args, { user }) => {
      if (!user) {
        return [];
      }
      const results = await client.query(
        q.Paginate(q.Match(q.Index('todos_by_user'), user)),
      );
      return results.data.map(([ref, text, done]) => ({
        id: ref.id,
        text,
        done,
      }));
    },
  },

  Mutation: {
    addTodo: async (_, { text }, { user }) => {
      if (!user) {
        throw new Error('voer ieks goeds in man');
      }
      const results = await client.query(
        q.Create(q.Collection('todos'), {
          data: { text, done: false, owner: user },
        }),
      );

      return {
        ...results.data,
        id: results.ref.id,
      };
    },

    updateTodoDone: async (_, { id }, { user }) => {
      if (!user) {
        throw new Error('voer ieks goeds in man ali boalowii');
      }
      const results = await client.query(
        q.Update(q.Ref(q.Collection('todos'), id), { data: { done: true } }),
      );
      return {
        ...results.data,
        id: results.ref.id,
      };
    },
  },
};

console.log('CLIENT grapqhl #mnfx', client);

// const apolloHandler = server.createHandler();

exports.handler = async (event, context) => {
  try {
    const server = new ApolloServer({
      typeDefs,
      resolvers,

      context: async () => {
        // console.log('ff checken event', event);
        console.log('ff checken context', context);

        if (context.clientContext.user) {
          return {
            event,
            context,
            user: context.clientContext.user.sub,
          };
        }
        return {};
      },

      // playground
      playground: [ApolloServerPluginLandingPageGraphQLPlayground()],
      introspection: true,
    });

    const apolloHandler = server.createHandler({
      expressGetMiddlewareOptions: {
        cors: {
          origin: '*',
          credentials: true,
        },
      },
    });

    console.log('before event ish:', event);
    console.log('before context ish:', context);

    return await apolloHandler(event, context);
  } catch (err) {
    return console.error('Failed ISH!', err);
  }
};

This is the wrapper file: (gatsby-browser.js)

/* eslint-disable prettier/prettier */
/* eslint-disable react/jsx-props-no-spreading */
const React = require('react');

const {
  ApolloProvider,
  ApolloClient,
  HttpLink,
  InMemoryCache,
} = require('@apollo/client');

require('bootstrap/dist/css/bootstrap.min.css');

const netlifyIdentity = require('netlify-identity-widget');

const { setContext } = require('@apollo/client/link/context');

// const fetch = require('isomorphic-fetch');
const fetch = require('cross-fetch');
// const fetch = require('node-fetch');

const { IdentityProvider } = require('./src/context/identity-context');

const Layout = require('./src/components/layout').default;

const authLink = setContext((_, { headers }) => {
  const user = netlifyIdentity.currentUser();
  const token = user.token.access_token;

  console.log('HEADERS @ gatsby-browser #mnfx:', headers);
  console.log('USER @ gatsby-browser #mnfx:', user);
  console.log('TOKEN @ gatsby-browser  #mnfx:', token);

  return {
    headers: {
      ...headers,
      Authorization: token ? `Bearer ${token}` : '',
    },
  };
});

const httpLink = new HttpLink({
  fetch,
  uri: 'https://afrodiasphere.netlify.app/.netlify/functions/graphql',
});


const client = new ApolloClient({
  ssrMode: true,
  cache: new InMemoryCache(),
  link: authLink.concat(httpLink),
});

const wrapPageElement = ({ element, props }) => (
  <Layout {...props}>{element}</Layout>
);

exports.wrapPageElement = wrapPageElement;

const wrapRootElement = ({ element }) => (
  <ApolloProvider client={client}>
    <IdentityProvider>{element}</IdentityProvider>
  </ApolloProvider>
);

exports.wrapRootElement = wrapRootElement;

// export const onPreRouteUpdate = ({ location, prevLocation }) => {
//   console.log('new pathname', location.pathname);
//   console.log('old pathname', prevLocation ? prevLocation.pathname : null);
// };

And to make it complete, also the front-end page: (dashboard.tsx)

import React, { useContext, useRef } from 'react';

import { gql, useMutation, useQuery } from '@apollo/client';
import { Button, Form, ListGroup, InputGroup } from 'react-bootstrap';

import IdentityContext from '../context/identity-context';

// interface Props {}

const ADD_TODO = gql`
  mutation AddTodo($text: String!) {
    addTodo(text: $text) {
      id
    }
  }
`;

const UPDATE_TODO_DONE = gql`
  mutation UpdateTodoDone($id: ID!) {
    updateTodoDone(id: $id) {
      text
      done
    }
  }
`;

const GET_TODOS = gql`
  query GetTodos {
    todos {
      id
      text
      done
    }
  }
`;

const Dashboard = () => {
  const { user, identity: netlifyIdentity } = useContext(IdentityContext);

  const inputRef = useRef();

  const [addTodo] = useMutation(ADD_TODO);
  const [updateTodoDone] = useMutation(UPDATE_TODO_DONE);
  const { loading, error, data, refetch } = useQuery(GET_TODOS);

  console.log(data);
  // console.log('displatch! :', dispatch, 'data! :', data);

  return (
    <div>
      <span>Hallo {user && user.user_metadata.full_name}, welkom!</span>

      {user && (
        <Button
          variant="outline-danger"
          type="button"
          className="px-4 mx-4"
          onClick={() => {
            netlifyIdentity.logout();
          }}
        >
          Log out {user.user_metadata.full_name}
        </Button>
      )}

      <div className="container my-5">
        <Form
          onSubmit={async (e) => {
            e.preventDefault();
            await addTodo({ variables: { text: inputRef.current.value } });
            inputRef.current.value = '';
            await refetch();
          }}
        >
          <Form.Group className="d-flex justify-content-between">
            <Form.Label htmlFor="add-die-todo">Add een todo </Form.Label>
            <Form.Control type="text" placeholder="Enter text" ref={inputRef} />
            <Button type="submit" className="mb-2">
              Submit
            </Button>
          </Form.Group>

          <div className="container mt-5 ">
            {loading ? <div>loading...</div> : null}
            {error ? <div>{error.message}</div> : null}
            {!loading && !error && (
              <ListGroup as="ul">
                {data.todos.map((todo) => (
                  <ListGroup.Item
                    eventKey={todo.id}
                    key={todo.id}
                    as="li"
                    className="d-flex justify-content-evenly"
                    onClick={async () => {
                      await updateTodoDone({ variables: { id: todo.id } });
                      await refetch();
                    }}
                  >
                    <InputGroup.Checkbox
                      aria-label="Checkbox for following text input"
                      checked={todo.done}
                    />
                    <span>{todo.text}</span>
                  </ListGroup.Item>
                ))}
                {console.log('DATA van dashboard #mnfx:', data)}
              </ListGroup>
            )}
          </div>
        </Form>
      </div>
    </div>
  );
};

export default Dashboard;

I think this must do it… Help is greatly wanted. Thanks in advance!

Hey @mikeyfe6,

I think this question is more suitable for apollo-sever-lambda as, just like you say this is caused by upgrading the version. So, there could be some breaking changes, but having no experience we the library, we can’t comment on that.

I’m a bit late to the party here but I ran into the same issue and it does seem to be at least a Netlify-specific issue.

Clicking things together – from v2 → v3 changed a lot for the wrapper around apollo server.

I also found a hacky workaround which so far seems to work for me. Doesn't work with netlify · Issue #427 · vendia/serverless-express · GitHub

If I understand it right, it’s just a small change within the environment?

Would be sad to see this not being addressed, as for me at least – this exactly what I use Netlify for.

I’m sorry, but I do not understand how that’s a Netlify issue. That’s how Netlify Functions were built. We have had our own context object. Netlify Functions were not advertised to support all of AWS Lambda features as a 1:1 mapping.

As mentioned in the linked issue (and I’ve been using it myself too), severless-http is able to detect it correctly without any additional config. This seems to me more like Apollo is not interested in allowing easy config for Netlify - even though it’s possible.

With that being said, we will try to run this with the devs. However, I’m not sure how much of an engineering effort this is, or if it will be considered give the low demand. So, this might or might not get prioritised.

I still feel it’s probably easier for the library to integrate this than Netlify to change a product behaviour (or introduce new behaviour) that a lot of people rely on.

Hey @dennisbochen,

We heard back from our engineers and this is what they say:

The AWS documentation doesn’t make it easy to find this information, but as far as I can tell the requestContext property is populated by API Gateway (which we don’t use), not Lambda. So I would say that using that property to detect the Lambda environment is a bad idea, and instead they should use process.env.LAMBDA_TASK_ROOT or any other of the environment variables populated by Lambda.

In short, this is something that Apollo needs to configure to work correctly on Netlify.

Hi @hrishikesh – thanks for getting back. I didn’t see your earlier message. And and special thanks for pointing the engineering team at this.

First - I didn’t say or intend to assert this is a netlify issue – it seems to be specific to netlify. And the explanation supports this. I’ll see if I can take this to them or live with that ugly workaround.

Thank you ver much :pray:

@hrishikesh I think it becomes a Netlify issue the moment when the page graphql - Netlify Functions links to outdated projects that use ALL the aforementioned apollo-server-lambda package. Which in turn, makes the developer (your client) think that he can easily deploy a GraphQL backend using Netlify Functions. And lose a day of work (personal experience).

I found a solution using stepzen plugin (maybe that’s why apollo-server-lambda support has been dropped or discontinued)

Hey @giulioprinaricotti,

I believe version 2 of Apollo still works, right?