Data not returned from REST datasource in Apollo server for graphQL - graphql

I am trying out the basic implementation for Apollo server for GraphQL with my REST API calls as Data Sources. I do not see any data returned from the same even though there is data returned when I call the API separately. Can anyone help figure out what could be going wrong?
PS: I have CORS enabled on my API so not sure if I am passing that too correctly. I do not have any idea how to figure out what URL this is calling.
My sample code below:
const { ApolloServer, gql } = require('apollo-server');
const { RESTDataSource } = require('apollo-datasource-rest');
class Contact extends RESTDataSource {
constructor() {
super();
this.baseURL = 'http://localhost:8080/objects/';
}
async getContactById(id) {
return this.get(`contact/${id}`);
}
async getAllContacts() {
const data = await this.get(`contact`);
return data.results;
}
// an example making an HTTP PUT request
async newContact(contact) {
return this.put(
'contact', // path
contact, // request body
);
}
};
// Type definitions define the "shape" of your data and specify
// which ways the data can be fetched from the GraphQL server.
const typeDefs = gql`
# Comments in GraphQL are defined with the hash (#) symbol.
type Query {
allContacts: [Contact]
contactById(id: ID): Contact
}
type Contact {
id: ID
contact_name: String
}
`;
// Resolvers define the technique for fetching the types in the
// schema.
const resolvers = {
Query: {
contactById: async (_source, { id }, { dataSources }) => {
return dataSources.contact.getContactById(id);
},
allContacts: async (_source, _args, { dataSources }) => {
return dataSources.contact.getAllContacts();
},
},
};
// In the most basic sense, the ApolloServer can be started
// by passing type definitions (typeDefs) and the resolvers
// responsible for fetching the data for those types.
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => {
return {
contact : new Contact(),
};
},
cors : true,
});
// This `listen` method launches a web-server. Existing apps
// can utilize middleware options, which we'll discuss later.
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Below is the request and response from the GraphQL playground:
query {
contactById (id : 5) {
id
contact_name
}
}
Response:
{
"data": {
"contactById": {
"id": null,
"contact_name": null
}
}
}

Related

Get headers with executeOperation on Apollo Server (apollo-server) for integrations tests

Since the deprecation of apollo-server-testing I am using the new way of doing integration tests with apollo-server (included in apollo-server 2.25.0). From the mutation signin I set my refresh token in the OutgoingMessage header's (in 'Set-Cookie').
Simplified resolver
#Mutation(() => RefreshTokenOutput)
async refreshToken(#Ctx() { response, contextRefreshToken }: Context): Promise<RefreshTokenOutput> {
if (contextRefreshToken) {
const { accessToken, refreshToken } = await this.authService.refreshToken(contextRefreshToken);
response.setHeader(
'Set-Cookie',
cookie.serialize('refreshToken', refreshToken, {
httpOnly: true,
maxAge: maxAge,
secure: true,
})
);
return { accessToken: accessToken };
} else {
throw new AuthenticationError();
}
}
Test case
// given:
const { user, clearPassword } = await userLoader.createUser16c();
const input = new UserSigninInput();
input.email = user.email;
input.password = clearPassword;
const MUTATE_signin = gql`
mutation signin($userInput: UserSigninInput!) {
signin(input: $userInput) {
accessToken
}
}
`;
// when:
const res = await server.executeOperation(
{ query: MUTATE_signin, variables: { userInput: input }, operationName: 'signin' },
buildContext(user)
);
I'm trying to test if this token is correctly set and well formed. Did you have any idea on how I can access this header with executeOperation ?
I was able to set headers like this:
const res = await apolloServer.executeOperation({ query: chicken, variables: { id: 1 } }, {req: {headers: 'Authorization sdf'}});
server.executeOperation calls processGraphQLRequest
and processGraphQLRequest return type is GraphQLResponse
export interface GraphQLResponse {
data?: Record<string, any> | null;
errors?: ReadonlyArray<GraphQLFormattedError>;
extensions?: Record<string, any>;
http?: Pick<Response, 'headers'> & Partial<Pick<Mutable<Response>, 'status'>>;
}
I'm not sure, but i think headers in GraphQLResponse.http
you can find call structure in github repo.
https://github.com/apollographql/apollo-server/blob/6b9c2a0f1932e6d8fb94a8662cc1da24980aec6f/packages/apollo-server-core/src/requestPipeline.ts#L126
Apollo defines executeOperation as:
public async executeOperation(
request: Omit<GraphQLRequest, 'query'> & {
query?: string | DocumentNode;
},
integrationContextArgument?: ContextFunctionParams,
) {
integrationContextArgument is optional and ContextFunctionParams is just an alias to any.
As mentioned in an answer above, any context JSON passed to the executeOperation function will be sent to Apollo's processGraphQLRequest() function
graphQLServerOptions() function processes that JSON.
For more advanced scenarios, it seems that a context resolver function, not just JSON context data, can be passed in using the context field
Reference: https://github.com/apollographql/apollo-server/blob/e9ae0f28d11d2fdfc5abd5048c85acf70de21592/packages/apollo-server-core/src/ApolloServer.ts#L1014

GraphQL - does context propagate to downstream resolvers?

If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers? Is this specified in the GraphQL specification or implementation specific?
To clarify with an example say I have a query like the following
{
companies {
employees {
positions {
title
}
}
}
}
let's say I start with contextA coming into the companies query and then I have my CompanyResolvers where I get a superSpecialContext and pass this on to the employees dataloader
export const CompanyResolvers = {
employees: async ({ id }: CompanyDTO, args: object, contextA: Context) => {
const superSpecialContext = await getSuperSpecialContext();
return context.dataLoaders.employees.load({ id: company.id, context: superSpecialContext });
}
};
when I get to the positions resolver am I now working with the superSpecialContext or the original contextA (I would actually prefer this to be the case)?
export const EmployeeResolvers = {
positions: async ({ id }: EmployeeDTO, args: object, context: Context) => {
// is my context here contextA or superSpecialContext?
}
};
If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers.
Yes, each request gets its own context object for the duration of the request. It gets created in the context function on the GraphQL server.
import { ApolloServer, gql } from 'apollo-server'
import { ExpressContext } from 'apollo-server-express/dist/ApolloServer';
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;
const books = [
{
title: 'Harry Potter and the Chamber of Secrets',
author: 'J.K. Rowling',
},
{
title: 'Jurassic Park',
author: 'Michael Crichton',
},
];
const resolvers = {
Query: {
books: (obj: any, args: any, context: any) => {
console.log(context.name); // Khalil Stemmler
context.name = 'Billy Bob Thorton'
return books;
},
},
Book: {
title: (obj: any, args: any, context: any) => {
console.log(context.name); // Billy Bob Thorton.
// Should print "Billy Bob Thorton twice", once for each book.
return obj.title
},
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: (expressContext: ExpressContext) => {
// The Apollo implementation of context allows you hook into the
// Express request to get access to headers, tokens, etc- in order
// to grab an authenticated user's session data and put it on context.
const { connection, res, req } = expressContext;
// A new context object is created for every request. This function
// should return an object.
return {
name: 'Khalil Stemmler'
}
}
});
// The `listen` method launches a web server.
server.listen().then(({ url }: { url: string }) => {
console.log(`🚀 Server ready at ${url}`);
});
Running the following query:
{
books {
title
author
}
}
We get:
🚀 Server ready at http://localhost:4000/
Khalil Stemmler
Billy Bob Thorton
Billy Bob Thorton
Reference: "Context Argument - Apollo Docs".

#client Apollo GQL tag breaks query

I have a vue-apollo (using nuxt) query that is supposed to have a local client field show. However, when I have the show #client line included in the query the component does not render. For some reason it also seems to fail silently.
query myAccounts {
accounts: myAccounts {
email
calendars {
id
name
hex_color
is_enabled
show #client
}
}
}
I am extending the Calendar type in an extensions.js file (pasted below) with two mutations.
import gql from 'graphql-tag'
export const typeDefs = gql`
extend type Calendar {
show: Boolean
}
type Mutation {
showCalendar(id: ID!): Boolean
hideCalendar(id: ID!): Boolean
}
`
Here is the resolver that sets the value, along with the Apollo config:
import { InMemoryCache } from 'apollo-cache-inmemory'
import { typeDefs } from './extensions'
import MY_ACCOUNTS_QUERY from '~/apollo/queries/MyAccounts'
const cache = new InMemoryCache()
const resolvers = {
Mutation: {
showCalendar: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: MY_ACCOUNTS_QUERY })
const found = data.accounts
.flatMap(({ calendars }) => calendars)
.find(({ id }) => id === '1842')
if (found) {
found.show = true
}
cache.writeQuery({ query: todoItemsQuery, data })
return true
}
}
}
export default context => {
return {
cache,
typeDefs,
resolvers,
httpLinkOptions: {
credentials: 'same-origin'
},
}
}
along with the nuxt config:
apollo: {
defaultOptions: {
$query: {
loadingKey: 'loading',
fetchPolicy: 'cache-and-network',
},
},
errorHandler: '~/plugins/apollo-error-handler.js',
clientConfigs: {
default: '~/apollo/apollo-config.js'
}
}
Querying local state requires the state to exist (i.e. it should be initialized) or for a local resolver to be defined for the field. Apollo will run the resolver first, or check the cache directly for the value if a resolver is not defined. There's not really a good way to initialize that value since it's nested inside a remote query, so you can add a resolver:
const resolvers = {
Calendar: {
show: (parent) => !!parent.show,
},
// the rest of your resolvers
}
See the docs for additional examples and more details.

Apollo Server - Apply Authentication to Certain Resolvers Only with Passport-JWT

I currently have a Node.js back-end running Express with Passport.js for authentication and am attempting to switch to GraphQL with Apollo Server. My goal is to implement the same authentication I am using currently, but cannot figure out how to leave certain resolvers public while enabling authorization for others. (I have tried researching this question extensively yet have not been able to find a suitable solution thus far.)
Here is my code as it currently stands:
My JWT Strategy:
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = JWT_SECRET;
module.exports = passport => {
passport.use(
new JwtStrategy(opts, async (payload, done) => {
try {
const user = await UserModel.findById(payload.sub);
if (!user) {
return done(null, false, { message: "User does not exist!" });
}
done(null, user);
} catch (error) {
done(err, false);
}
})
);
}
My server.js and Apollo configuration:
(I am currently extracting the bearer token from the HTTP headers and passing it along to my resolvers using the context object):
const apollo = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
let authToken = "";
try {
if (req.headers.authorization) {
authToken = req.headers.authorization.split(" ")[1];
}
} catch (e) {
console.error("Could not fetch user info", e);
}
return {
authToken
};
}
});
apollo.applyMiddleware({ app });
And finally, my resolvers:
exports.resolvers = {
Query: {
hello() {
return "Hello world!";
},
async getUserInfo(root, args, context) {
try {
const { id } = args;
let user = await UserModel.findById(id);
return user;
} catch (error) {
return "null";
}
},
async events() {
try {
const eventsList = await EventModel.find({});
return eventsList;
} catch (e) {
return [];
}
}
}
};
My goal is to leave certain queries such as the first one ("hello") public while restricting the others to requests with valid bearer tokens only. However, I am not sure how to implement this authorization in the resolvers using Passport.js and Passport-JWT specifically (it is generally done by adding middleware to certain endpoints, however since I would only have one endpoint (/graphql) in this example, that option would restrict all queries to authenticated users only which is not what I am looking for. I have to perform the authorization in the resolvers somehow, yet not sure how to do this with the tools available in Passport.js.)
Any advice is greatly appreciated!
I would create a schema directive to authorized query on field definition and then use that directive wherever I want to apply authorization. Sample code :
class authDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
type._requiredAuthRole = this.args.requires;
}
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType);
field._requiredAuthRole = this.args.requires;
}
ensureFieldsWrapped(objectType) {
// Mark the GraphQLObjectType object to avoid re-wrapping:
if (objectType._authFieldsWrapped) return;
objectType._authFieldsWrapped = true;
const fields = objectType.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const {
resolve = defaultFieldResolver
} = field;
field.resolve = async function (...args) {
// your authorization code
return resolve.apply(this, args);
};
});
}
}
And declare this in type definition
directive #authorization(requires: String) on OBJECT | FIELD_DEFINITION
map schema directive in your schema
....
resolvers,
schemaDirectives: {
authorization: authDirective
}
Then use it on your api end point or any object
Query: {
hello { ... }
getuserInfo():Result #authorization(requires:authToken) {...}
events():EventResult #authorization(requires:authToken) {...}
};

Apollo GraphQL server; setting context to handle requests triggered by a fired subscription

I understand how to set the context object when creating a GraphQL server e.g.
const app = express();
app.use(GRAPHQL_URL, graphqlExpress({
schema,
context: {
foo: 'bar'
},
}));
so that the context object is passed to my resolvers when handling an incoming request.
However I'm not seeing this context object when the resolvers are triggered by a subscription (i.e. a client subscribes to a GraphQL subscription, and defines the shape of the data to be sent to them when the subscription fires); in that case the context appears to be an empty Object.
Is there way to ensure that my context object is set correctly when resolvers are called following a PubSub.publish() call?
I guess you are using the package subscription-transport-ws. In that case it is possible to add a context value in different execution steps.
See API. Two possible scenarios
If you have some kind of authentication. You could add a viewer in the context at the onConnect execution step. This is done at the first connection to the websocket and wont change until the connection is closed and opened again. See example.
If you want to add a context more dynamically you can add a kind of middleware before the execute step.It could look like this:
const middleware = (args) => new Promise((resolve, reject) => {
const [schema, document, root, context, variables, operation] = args;
context.foo = "bar"; // add something to context
resolve(args);
})
subscriptionServer = SubscriptionServer.create({
schema: executable.schema,
subscribe,
execute: (...args) => middleware(args).then(args => {
return execute(...args);
})
}, {
server: websocketServer,
path: "/graphql",
}, );
Here is my solution:
You can pass the context and do the authentication for graphql subscription(WebSocket )like this:
const server = new ApolloServer({
typeDefs,
resolvers,
context: contextFunction,
introspection: true,
subscriptions: {
onConnect: (
connectionParams: IWebSocketConnectionParams,
webSocket: WebSocket,
connectionContext: ConnectionContext,
) => {
console.log('websocket connect');
console.log('connectionParams: ', connectionParams);
if (connectionParams.token) {
const token: string = validateToken(connectionParams.token);
const userConnector = new UserConnector<IMemoryDB>(memoryDB);
let user: IUser | undefined;
try {
const userType: UserType = UserType[token];
user = userConnector.findUserByUserType(userType);
} catch (error) {
throw error;
}
const context: ISubscriptionContext = {
// pubsub: postgresPubSub,
pubsub,
subscribeUser: user,
userConnector,
locationConnector: new LocationConnector<IMemoryDB>(memoryDB),
};
return context;
}
throw new Error('Missing auth token!');
},
onDisconnect: (webSocket: WebSocket, connectionContext: ConnectionContext) => {
console.log('websocket disconnect');
},
},
});
You can pass the context argument of resolver using pubsub.publish method in your resolver like this:
addTemplate: (
__,
{ templateInput },
{ templateConnector, userConnector, requestingUser }: IAppContext,
): Omit<ICommonResponse, 'payload'> | undefined => {
if (userConnector.isAuthrized(requestingUser)) {
const commonResponse: ICommonResponse = templateConnector.add(templateInput);
if (commonResponse.payload) {
const payload = {
data: commonResponse.payload,
context: {
requestingUser,
},
};
templateConnector.publish(payload);
}
return _.omit(commonResponse, 'payload');
}
},
Now, we can get the http request context and subscription(websocket) context in
your Subscription resolver subscribe method like this:
Subscription: {
templateAdded: {
resolve: (
payload: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
args: any,
subscriptionContext: ISubscriptionContext,
info: any,
): ITemplate => {
return payload.data;
},
subscribe: withFilter(templateIterator, templateFilter),
},
},
async function templateFilter(
payload?: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
args?: any,
subscriptionContext?: ISubscriptionContext,
info?: any,
): Promise<boolean> {
console.count('templateFilter');
const NOTIFY: boolean = true;
const DONT_NOTIFY: boolean = false;
if (!payload || !subscriptionContext) {
return DONT_NOTIFY;
}
const { userConnector, locationConnector } = subscriptionContext;
const { data: template, context } = payload;
if (!subscriptionContext.subscribeUser || !context.requestingUser) {
return DONT_NOTIFY;
}
let results: IUser[];
try {
results = await Promise.all([
userConnector.findByEmail(subscriptionContext.subscribeUser.email),
userConnector.findByEmail(context.requestingUser.email),
]);
} catch (error) {
console.error(error);
return DONT_NOTIFY;
}
//...
return true;
}
As you can see, now we get the subscribe users(who establish the WebSocket connection with graphql webserver) and HTTP request user(who send the mutation to graphql webserver) from subscriptionContext and HTTP request context.
Then you can do the rest works if the return value of templateFilter function is truthy, then WebSocket will push message to subscribe user with payload.data, otherwise, it won't.
This templateFilter function will be executed multiple times depending on the count of subscribing users which means it's iterable. Now you get each subscribe user in this function and does your business logic to decide if push WebSocket message to the subscribe users(client-side) or not.
See github example repo
Articles:
GraphQL Subscription part 1
GraphQL Subscription part 2
If you're using Apollo v3, and graphql-ws, here's a docs-inspired way to achieve context resolution:
const wsContext = async (ctx, msg, args) => {
const token = ctx.connectionParams.authorization;
const currentUser = await findUser(token);
if(!currentUser) throw Error("wrong user token");
return { currentUser, foo: 'bar' };
};
useServer(
{
schema,
context: wsContext,
}
wsServer,
);
You could use it like so in your Apollo React client:
import { GraphQLWsLink } from '#apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4000/subscriptions',
connectionParams: {
authorization: user.authToken,
},
}));

Resources