Apollo client: Can #defer be used with client side resolvers? - graphql

For some reason, I had to build a client-side only GraphQL server, my schema is built as follow:
private buildSchema(): GraphQLSchema {
const allTypes: string = ...// my types
const allResolvers: IResolvers[] = ...// my resolvers
return makeExecutableSchema({
typeDefs: allTypes,
resolvers: allResolvers
});
}
The client is as follow:
this.client = new ApolloClient({
link: new SchemaLink({schema: this.buildSchema()}),
cache: new InMemoryCache({
addTypename: false
})
});
And everything works fine except that my queries are not defered. For instance if I run:
const gqlQuery: string = `
{
user {
name
slowResolver #defer {
text
}
}
}
`
const $result = this.apollo.getClient().watchQuery({
query: gql(gqlQuery)
});
The $result will be emited only when the whole query will be resolved (instead of user and then slowResolver as expected).
Any idea of what I missed in the workflow?

The #defer directive was actually removed from Apollo, although there's been some work done to reimplement it. Even if it's implemented, though, deferred queries would have to be handled outside of the execution context. In other words, executing the schema can return a deferred execution result, but something else (like Apollo server itself) has to handle how that response (both the initial payload, and the subsequent patches) are actually sent to the server over whatever transport.
If you're defining a schema client-side, unfortunately, it's not going to be possible to use the #defer directive.

Related

Is it possible to query my running apollo graphqlserver locally, without using http?

I'm running a Graphql server from Apollo, and the objective is fetch some data. However, I need this data locally - on the same server. Is that possible, or is the only way to query the Apollo server using http?
I know that I could possible accomplish this without using GraphQl, and just access the data layer, but the thing is that I would like to benefit from:
Authorization
Dataloaders
Already built-in optimization in our Graphql Api
I already have a working solution where I just use node-fetch to query localhost, but it seems like quite a bit of overhead.
Yes it is possible!
Apollo makes the schema building and execution for you, but you can also do it yourself.
Here is a mini example based on the apollo-server-express package. I create the schema and then give it to the apollo-server. Look below the server startup, I also create a query-string, then parse it and execute it without apollo and without an http request.
const express = require('express');
const { ApolloServer, gql, makeExecutableSchema } = require('apollo-server-express');
const { parse } = require('graphql/language')
const { execute } = require('graphql')
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
hello: String
}
`;
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
};
const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
async function startApolloServer() {
const server = new ApolloServer({ schema });
await server.start();
const app = express();
server.applyMiddleware({ app });
await new Promise(resolve => app.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
return { server, app };
}
startApolloServer()
const query = `
query {
hello
}
`
const document = parse(query)
const res = execute({
schema,
document,
})
console.log('res no request:', res)
if you run it, install apollo-server-express and graphql with npm and you are good to go
To execute you can pass all your request logic as well:
execute({
schema,
document,
rootValue: {},
contextValue: {
userInfo,
dbClient,
},
variableValues: body.variables,
}),
It is highly useful also if you want to test you server. If you need to do subscriptions you can use the subscribe method imported from graphql as well.

Querying for a local state variable doesn't immediately return a result

I'm querying for my local variable isLeftSidebarOpen and I thought that it should immediately return result without loading state. isLeftSidebarOpen is initialized when ApolloClient is created.
const data = {
isLeftSidebarOpen: false,
};
const initializeCache = cache => {
cache.writeData({ data });
};
const cache = new InMemoryCache();
const client = new ApolloClient({
link: new HttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'include',
}),
cache,
resolvers,
});
initializeCache(cache);
query IsLeftSidebarOpen {
isLeftSidebarOpen #client
}
const { data, loading } = useQuery(IS_LEFTSIDEBAR_OPEN);
console.log(data);
console.log(loading);
The result is:
undefined
true
{ isLeftSidebarOpen: false }
false
Whereas I expected to be:
{ isLeftSidebarOpen: false }
false
What is wrong with my understanding?
The local resolver interface has to be asynchronous as apollo client can simply not know wether you'll implement your local resolvers asynchronoulsy or not. Asynchronous works in both scenarios, synchronous does not. Even if your implementation itself is synchronous, apollo will await that function before setting loading to false.
Another "benefit" is that your query behaves the exact same way, regardless of it being resolved locally or remote.
However, cache.readQuery is a synchronous operation, so you can use that instead of using a local resolver to return the data immediately, given that you get a cache hit.

Apollo graphql: makeExecutableSchema & playground

Im a beginner trying to set up a graphql API with apollo-express and prisma
All was going well, but than I decided to use this library to add input validation:
graphql-constraint-directive
It requires me to use makeExecutableSchema to build my schema so I can use the schemaDirectives param
My code to start the server was like this:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({req}) => {
const userId = getUserID(req);
return {
userId,
prisma
}
}
});
And all was working perfectly
But to use that lib I had it refactored it like this:
const schema = makeExecutableSchema({
typeDefs,
resolvers,
schemaDirectives: {constraint: ConstraintDirective}
});
const server = new ApolloServer({
schema,
context: ({req}) => {
const userId = getUserID(req);
return {
userId,
prisma
}
}
});
And it works too, all my queries and mutations are working, and the validation too.
But it broke the graphql-playground: its no longer able to load my schema and docs, both tabs are empty. And it displays the following error:
Server cannot be reached
It still works: Im able to send my querys and mutations and all, but I no longer have code completion and the auto docs, since it doenst knows my schema, and so is no longer as useful
If I replace the executable schema for the pure typeDefs ans resolvers, than it works alright again, playground loads everything again
Am I supposed to do something different when using makeExecutableSchema, for playgroun to work?
Whether you use makeExecutableSchema or pass the typeDefs and resolvers to the ApolloServer constructor directly shouldn't matter -- Apollo Server uses makeExecutableSchema under the hood anyway. In fact, you can just pass the directive map to the constructor directly:
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {constraint: ConstraintDirective}
context: ({req}) => {
const userId = getUserID(req);
return {
userId,
prisma
}
}
});
This is a bug with the library you're using. The directive replaces the built-in scalars with custom ones, but doesn't actually add those custom scalars to the schema. When Playground tries to introspect the schema, it can't find the custom scalars in the introspection results and errors out. I would avoid that particular library -- it doesn't look like it's actively maintained.

Log Query/Mutation actions to database for Auditing

My goal is to run some kind of webhook, cloud function or say I want to perform some kind of action after each query success or mutation success in graphql.
Means I want to log each and every action performed by users (kind of history of when what was created and updated).
How can this be implemented using some kind of middleware between graphql and DB (say mongo for now)?
Means that middleware should be responsible to run the logging action each time a query or mutation is called from front-end.
Tech stack being used is- Node, express, graphQl, Redis etc.
Any suggestions would really be appreciated.
Thanks
The solution I came up with was calling a function manually each time a query or mutate.
If you're using Apollo, you can utilize the formatResponse and formatError options for logging, as outlined in the docs.
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: error => {
console.log(error);
return error;
},
formatResponse: response => {
console.log(response);
return response;
},
});
Using an extension can allow you to hook into different phases of the GraphQL request and allow more granular logging. A simple example:
const _ = require('lodash')
const { GraphQLExtension } = require('graphql-extensions')
module.exports = class LoggingExtension extends GraphQLExtension {
requestDidStart(options) {
logger.info('Operation: ' + options.operationName)
}
willSendResponse(o) {
const errors = _.get(o, 'graphqlResponse.errors', [])
for (const error of errors) {
logger.error(error)
}
}
}
There's a more involved example here. You can then add your extension like this:
const server = new ApolloServer({
typeDefs,
resolvers,
extensions: [() => new YourExtension()]
});
If you're using express-graphql to serve your endpoint, your options are a bit more limited. There's still a formatError option, but no formatResponse. There is a way to pass in an extensions array as well, but the API is different from Apollo's. You can take a look at the repo for more info.

Log apollo-server GraphQL query and variables per request

When using apollo-server 2.2.1 or later, how can one log, for each request, the query and the variables?
This seems like a simple requirement and common use case, but the documentation is very vague, and the query object passed to formatResponse no longer has the queryString and variables properties.
Amit's answer works (today), but IMHO it is a bit hacky and it may not work as expected in the future, or it may not work correctly in some scenarios.
For instance, the first thing that I thought when I saw it was: "that may not work if the query is invalid", it turns out that today it does work when the query is invalid. Because with the current implementation the context is evaluated before the the query is validated. However, that's an implementation detail that can change in the future. For instance, what if one day the apollo team decides that it would be a performance win to evaluate the context only after the query has been parsed and validated? That's actually what I was expecting :-)
What I'm trying to say is that if you just want to log something quick in order to debug something in your dev environment, then Amit's solution is definitely the way to go.
However, if what you want is to register logs for a production environment, then using the context function is probably not the best idea. In that case, I would install the graphql-extensions and I would use them for logging, something like:
const { print } = require('graphql');
class BasicLogging {
requestDidStart({queryString, parsedQuery, variables}) {
const query = queryString || print(parsedQuery);
console.log(query);
console.log(variables);
}
willSendResponse({graphqlResponse}) {
console.log(JSON.stringify(graphqlResponse, null, 2));
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
extensions: [() => new BasicLogging()]
});
Edit:
As Dan pointed out, there is no need to install the graphql-extensions package because it has been integrated inside the apollo-server-core package.
With the new plugins API, you can use a very similar approach to Josep's answer, except that you structure the code a bit differently.
const BASIC_LOGGING = {
requestDidStart(requestContext) {
console.log("request started");
console.log(requestContext.request.query);
console.log(requestContext.request.variables);
return {
didEncounterErrors(requestContext) {
console.log("an error happened in response to query " + requestContext.request.query);
console.log(requestContext.errors);
}
};
},
willSendResponse(requestContext) {
console.log("response sent", requestContext.response);
}
};
const server = new ApolloServer(
{
schema,
plugins: [BASIC_LOGGING]
}
)
server.listen(3003, '0.0.0.0').then(({ url }) => {
console.log(`GraphQL API ready at ${url}`);
});
If I had to log the query and variables, I would probably use apollo-server-express, instead of apollo-server, so that I could add a separate express middleware before the graphql one that logged that for me:
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const { typeDefs, resolvers } = require('./graphql')
const server = new ApolloServer({ typeDefs, resolvers })
const app = express()
app.use(bodyParser.json())
app.use('/graphql', (req, res, next) => {
console.log(req.body.query)
console.log(req.body.variables)
return next()
})
server.applyMiddleware({ app })
app.listen({ port: 4000}, () => {
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
})
Dan's solution mostly resolves the problem but if you want to log it without using express,
you can capture it in context shown in below sample.
const server = new ApolloServer({
schema,
context: params => () => {
console.log(params.req.body.query);
console.log(params.req.body.variables);
}
});
I found myself needing something like this but in a more compact form - just the query or mutation name and the ID of the user making the request. This is for logging queries in production to trace what the user was doing.
I call logGraphQlQueries(req) at the end of my context.js code:
export const logGraphQlQueries = ( req ) => {
// the operation name is the first token in the first line
const operationName = req.body.query.split(' ')[0];
// the query name is first token in the 2nd line
const queryName = req.body.query
.split('\n')[1]
.trim()
.split(' ')[0]
.split('(')[0];
// in my case the user object is attached to the request (after decoding the jwt)
const userString = req.user?.id
? `for user ${req.user.id}`
: '(unauthenticated)';
console.log(`${operationName} ${queryName} ${userString}`);
};
This outputs lines such as:
query foo for user e0ab63d9-2513-4140-aad9-d9f2f43f7744
Apollo Server exposes a request lifecycle event called didResolveOperation at which point the requestContext has populated properties called operation and operationName
plugins: [
{
requestDidStart(requestContext) {
return {
didResolveOperation({ operation, operationName }) {
const operationType = operation.operation;
console.log(`${operationType} recieved: ${operationName}`)
}
};
}
}
]
// query recieved: ExampleQuery
// mutation recieved: ExampleMutation

Resources