Which `apollo-server-express` Version Work Best For These Apollo Server Packages? - aws-lambda

I’m trying to get apollo-server-lambda or apollo-server-express to work with an executable schema for v3.36.
Here are the packages we use:
apollo-server-express#3.36 or apollo-server-lambda#3+
graphql-constraint-directive#3.0.0
#graphql-tools/schema#7.1.3
I ran multiple regression test to make it work, and it does not seem to hit GraphQL.
Here’s my Apollo server config:
const apolloServer = new ApolloServer({
schema: initializeSchema(),
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground(),
{
didEncounterErrors(errors) {
logger.info(`didEncounterErrors:`)
logger.info(errors)
},
async requestDidStart(requestContext) {
logger.info(`Request started! ${requestContext}`);
return {
async parsingDidStart(requestContext) {
logger.info(`Parsing started! ${requestContext}`);
},
async validationDidStart(requestContext) {
logger.info(`Validation started! ${requestContext}`);
}
}
},
}],
context: async ({ event, context, express }) => {
logger.info(`Loading event... ${JSON.stringify(event)}`)
const newContext = {
headers: event.headers,
functionName: context.functionName,
event,
context,
expressRequest: express.req,
user: {} ?? null,
}
logger.info(`context ${JSON.stringify(newContext)}`)
return newContext
},
dataSources: () => {
logger.info('!initializing datasource')
initializeDbConnection()
return {}
},
...(['staging', 'production', 'demo'].includes(process.env.stage as string)
? { introspection: false, playground: false }
: {}),
})
I was able to log the executable schema inside initializeSchema, but it does not seem to hit the GraphQL Typedef and Resolver after upgrading. It just goes straight to context. So, I'm kinda stumped how to make HTTP request hit the Typedef and Resolvers using makeExecutableSchema()
I just need some advise or a list of table that could help me which version works best with the given apollo-server-<server_version>.

Related

apollo-server-lambda: Unable to determine event source based on event

I am using apollo-server-lambda for my app. I have create custom authoization http headers and it is required . if authoization: LETMEIN then it will return true and also return all data, if there is no any authoization or wrong authoization then it wll throw an error. For local development I used serverless-offline.In Local environment, it works as expected and here is the image but when I deploy my code to AWS, the api end does not work. It always throws me the error: here is the link.
I test my function AWS console. I am getting this error:
I did not get what I am doing wrong.
Here is my code
/* eslint-disable #typescript-eslint/no-var-requires */
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';
import { ApolloServer, AuthenticationError } from 'apollo-server-lambda';
import schema from '../graphql/schema';
import resolvers from '../resolvers';
import runWarm from '../utils/run-warm';
export const authToken = (token: string) => {
if (token === 'LETMEIN') {
return;
} else {
throw new AuthenticationError('No authorization header supplied');
}
};
const server = new ApolloServer({
typeDefs: schema,
resolvers,
debug: false,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
context: ({ event }) => {
//console.log(context);
if (event.headers) {
authToken(event.headers.authorization);
}
},
});
export default runWarm(
server.createHandler({
expressGetMiddlewareOptions: {
cors: {
origin: '*',
credentials: true,
allowedHeaders: ['Content-Type', 'Origin', 'Accept'],
optionsSuccessStatus: 200,
maxAge: 200,
},
},
})
);
This is my Lambda function
/**
* Running warm functions help prevent cold starts
*/
const runWarm =
(lambdaFunc: AWSLambda.Handler): AWSLambda.Handler =>
(event, context, callback) => {
// Detect the keep-alive ping from CloudWatch and exit early. This keeps our
// lambda function running hot.
if (event.source === 'serverless-plugin-warmup') {
return callback(null, 'pinged');
}
return lambdaFunc(event, context, callback);
};
export default runWarm;
This is not a direct answer, but might help, and could be useful if anyone else (like me) found this thread because of the error "Unable to determine event source based on event" when using apollo-server-lambda.
That error is coming from #vendia/serverless-express which is being used by apollo-server-lambda.
Within serverless-express, in src/event-sources/utils.js, there is a function called getEventSourceNameBasedOnEvent(), which is throwing the error. It needs to find something in the event object, and after a bit of experimentation I found that writing the lambda function like this solved the issue for me:
const getHandler = (event, context) => {
const server = new ApolloServer({
typeDefs,
resolvers,
debug: true,
});
const graphqlHandler = server.createHandler();
if (!event.requestContext) {
event.requestContext = context;
}
return graphqlHandler(event, context);
}
exports.handler = getHandler;
Note that the context object is added to the event object with the key "requestContext"....that's the fix.
(Also note that I have defined typeDefs and resolvers elsewhere in the code)
I can't guarantee this is the ideal thing to do, but it did work for me.

Getting test coverage of GraphQL resolve function

I'm using mocha chai and supertest to test a new graphql endpoint set up on our Node/Express server.
I have all the tests running and passing accordingly but when I run the following script:
"test-coverage": "nyc mocha tests/ --recursive",
it is not counting the tests for users resolve function in the code coverage.
My graphql query endpoint looks as shown below:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: () => ({
users: {
type: new GraphQLList(UserType),
args: {
searchByName: { type: GraphQLString },
queryNearbyUsers: { type: GraphQLBoolean },
skip: { type: GraphQLInt },
limit: { type: GraphQLInt }
},
async resolve(parent, args, req) {
const { searchByName, queryNearbyUsers, skip = 0, limit = 20 } = args
// No search criteria was specified so just return an error
if(!searchByName && !queryNearbyUsers)
throw new Error('NO_SEARCH_CRITERIA_SPECIFIED')
...
}
},
...
})
An example of one of my tests:
it('should throw an error (NO_SEARCH_CRITERIA_SPECIFIED) when no params supplied', function(done) {
request.post('spikeql')
.set({ Authorization: `Bearer ${token}`})
.send({ query: '{ users { _id firstName lastName displayName rpr distanceAway avatar { styles { thumb_square }}}}'})
.expect(200) // TODO: Setup GraphQL to match approproate HTTP res codes
.end((err, res) => {
if(err) return done(err)
let errors = res.body.errors
expect(errors).to.be.an('array')
// Check to make sure error was sent properly
expect(errors[0]).to.have.property('message', 'NO_SEARCH_CRITERIA_SPECIFIED')
expect(errors[0].message).to.be.a('string')
done()
})
})
I perform 3 other tests with different inputs for the GET_USERS query. All of them pass. It just doesn't get tracked in coverage report.
New to graphql and unit/integration testing so any help is appreciated. Can supply additional info if needed.

Pubsub publish multiple events Apollo Server

I am using Apollo Server and I want to publish 2 events in the row from same resolver. Both subscriptions are working fine but only if I dispatch only one event. If I try to dispatch both, second subscription resolver never gets called. If I comment out the first event dispatch second works normally.
const publishMessageNotification = async (message, me, action) => {
const notification = await models.Notification.create({
ownerId: message.userId,
messageId: message.id,
userId: me.id,
action,
});
// if I comment out this one, second pubsub.publish starts firing
pubsub.publish(EVENTS.NOTIFICATION.CREATED, {
notificationCreated: { notification },
});
const unseenNotificationsCount = await models.Notification.find({
ownerId: notification.ownerId,
isSeen: false,
}).countDocuments();
console.log('unseenNotificationsCount', unseenNotificationsCount);// logs correct value
// this one is not working if first one is present
pubsub.publish(EVENTS.NOTIFICATION.NOT_SEEN_UPDATED, {
notSeenUpdated: unseenNotificationsCount,
});
};
I am using default pubsub implementation. There are no errors in the console.
import { PubSub } from 'apollo-server';
import * as MESSAGE_EVENTS from './message';
import * as NOTIFICATION_EVENTS from './notification';
export const EVENTS = {
MESSAGE: MESSAGE_EVENTS,
NOTIFICATION: NOTIFICATION_EVENTS,
};
export default new PubSub();
Make sure, that you use pubsub from context of apollo server, for example:
Server:
const server = new ApolloServer({
schema: schemaWithMiddleware,
subscriptions: {
path: PATH,
...subscriptionOptions,
},
context: http => ({
http,
pubsub,
redisCache,
}),
engine: {
apiKey: ENGINE_API_KEY,
schemaTag: process.env.NODE_ENV,
},
playground: process.env.NODE_ENV === 'DEV',
tracing: process.env.NODE_ENV === 'DEV',
debug: process.env.NODE_ENV === 'DEV',
});
and example use in resolver, by context:
...
const Mutation = {
async createOrder(parent, { input }, context) {
...
try {
...
context.pubsub.publish(CHANNEL_NAME, {
newMessage: {
messageCount: 0,
},
participants,
});
dialog.lastMessage = `{ "orderID": ${parentID}, "text": "created" }`;
context.pubsub.publish(NOTIFICATION_CHANNEL_NAME, {
notification: { messageCount: 0, dialogID: dialog.id },
participants,
});
...
}
return result;
} catch (err) {
log.error(err);
return sendError(err);
}
},
};
...
It has been a while since this moment.
I have also been a struggle with pubsub not working problem.
and I would like to see your ApolloClient setup code.
I changed my configurations with regard to graphql version and client-side setup.
graphql version : 14.xx.xx -> 15.3.0
const client = new ApolloClient({
uri: 'http://localhost:8001/graphql',
cache: cache,
credentials: 'include',
link: ApolloLink.from([wsLink, httpLink])
});
I want you to clarify link order, especially about httpLink, if you use in your case, "HttpLink is a terminating Link.", according to Apollo official site.
At first, I used link order [httpLink, wsLink].
Therefore, pubsub.publish didn't work.
I hope this answer will help some of graphql users.

nuxt.js + Apollo Client: How to disable cache?

I managed to get an nuxt.js + nest.js with typescript and apollo graphql running.
To test if graphql works, i used the files from this example, and added a Button to the nuxt.js-page (on:click -> load all cats via graphql).
Everything works, reading and writing.
The problem is that after doing a mutation via playground or restarting the nest.js server with other graphql-data, the nuxt.js-page is displaying the old data(on click). I have to reload the whole page in the browser, to get the Apollo-Client fetching the new data.
I've tried to add a 'no-cache'-flag and 'network-only'-flag to nuxt.config.ts without success:
apollo: {
defaultOptions: {
$query: {
loadingKey: 'loading',
fetchPolicy: 'no-cache'
}
},
clientConfigs: {
default: {
httpEndpoint: 'http://localhost:4000/graphql',
wsEndpoint: 'ws://localhost:4000/graphql'
}
}
}
The function to get the cats:
private getCats() {
this.$apollo.query({ query: GET_CATS_QUERY }).then((res:any) => {
alert(JSON.stringify(res.data, null, 0));
});
}
How can I disable the cache or is there an other solution?
I had a similar problem recently and managed to fix it by creating a Nuxt plugin which overrides default client's options:
// plugins/apollo-overrides.ts
import { Plugin } from '#nuxt/types';
const apolloOverrides: Plugin = ({ app }) => {
// disable caching on all the queries
app.apolloProvider.defaultClient.defaultOptions = {
query: {
fetchPolicy: 'no-cache',
},
};
};
export default apolloOverrides;
Don't forget to register it in Nuxt's config:
// nuxt.config.js
export default {
...
plugins: [
'~/plugins/apollo-overrides',
],
...
};
I had problem like this you can fix it easily with remove $ before query
defaultOptions: {
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all'
}
},
And Reopen your dev server
If this solution not working add fetch policy for each query
.query({
query: sample,
variables: {},
errorPolicy: "all",
fetchPolicy: "no-cache"
})

How do you make Schema Stitching in Apollo Server faster?

Initially, I tried to use a Serverless Lambda function to handle schema stitching for my APIs, but I started to move toward an Elastic Beanstalk server to keep from needing to fetch the initial schema on each request.
Even so, the request to my main API server is taking probably ten times as long to get the result from one of the child API servers as my child servers do. I'm not sure what is making the request so long, but it seems like there is something blocking the request from resolving quickly.
This is my code for the parent API:
import * as express from 'express';
import { introspectSchema, makeRemoteExecutableSchema, mergeSchemas } from 'graphql-tools';
import { ApolloServer } from 'apollo-server-express';
import { HttpLink } from 'apollo-link-http';
import fetch from 'node-fetch';
async function run () {
const createRemoteSchema = async (uri: string) => {
const link = new HttpLink({ uri, fetch });
const schema = await introspectSchema(link);
return makeRemoteExecutableSchema({
schema,
link
});
};
const remoteSchema = await createRemoteSchema(process.env.REMOTE_URL);
const schema = mergeSchemas({
schemas: [remoteSchema]
});
const app = express();
const server = new ApolloServer({
schema,
tracing: true,
cacheControl: true,
engine: false
});
server.applyMiddleware({ app });
app.listen({ port: 3006 });
};
run();
Any idea why it is so slow?
UPDATE:
For anyone trying to stitch together schemas on a local environment, I got a significant speed boost by fetching 127.0.0.1 directly instead of going through localhost.
http://localhost:3002/graphql > http://127.0.0.1:3002/graphql
This turned out not to be an Apollo issue at all for me.
I'd recommend using Apollo engine to observe what is really going on with each request as you can see on the next screenshot:
you can add it to your Apollo Server configuration
engine: {
apiKey: "service:xxxxxx-xxxx:XXXXXXXXXXX"
},
Also, I've experienced better performance when defining the defaultMaxAge on the cache controle:
cacheControl: {
defaultMaxAge: 300, // 5 min
calculateHttpHeaders: true,
stripFormattedExtensions: false
},
the other thing that can help is to add longer max cache age on stitched objects if it does make sense, you can do this by adding cache hints in the schema stitching resolver:
mergeSchemas({
schemas: [avatarSchema, mediaSchema, linkSchemaDefs],
resolvers: [
{
AvatarFlatFields: {
faceImage: {
fragment: 'fragment AvatarFlatFieldsFragment on AvatarFlatFields { faceImageId }',
resolve(parent, args, context, info) {
info.cacheControl.setCacheHint({maxAge: 3600});
return info.mergeInfo.delegateToSchema({
schema: mediaSchema,
operation: 'query',
fieldName: 'getMedia',
args: {
mediaId: parseInt(parent.faceImageId),
},
context,
info,
});
}
},
}
},
Finally, Using dataLoaders can make process requests much faster when enabling batch processing and dataloaders caching read more at their github and the code will be something like this:
public avatarLoader = (context): DataLoader<any, any> => {
return new DataLoader(ids => this.getUsersAvatars(dataLoadersContext(context), ids)
.then(results => new Validation().validateDataLoaderArrayResults(ids, results))
, {batch: true, cache: true});
};

Resources