Remote schema stitching with latest versions of Apollo and GraphQL tools - graphql

i'm having a hard time figuring out the problem with my setup. I have been looking at the documentations but between apollo and graphql-tools the APIs changed frequently.
When i run this script, the console says "Error: Query root type must be provided."
import { ApolloServer } from "apollo-server";
import { loadSchema } from "#graphql-tools/load";
import { UrlLoader } from "#graphql-tools/url-loader";
import { stitchSchemas } from "#graphql-tools/stitch";
import fetch from "node-fetch";
import dotenv from "dotenv";
dotenv.config({ path: "../.env" });
async function startServer() {
const shopifySchema = await loadSchema(process.env.SHOPIFY_STOREFRONT_URL, {
loaders: [new UrlLoader()],
headers: {
"X-Shopify-Storefront-Access-Token":
process.env.SHOPIFY_STOREFRONT_API_TOKEN,
},
fetch,
});
const contentfulSchema = await loadSchema(process.env.CONTENTFUL_API_URL, {
loaders: [new UrlLoader()],
headers: {
Authorization: `Bearer ${process.env.CONTENTFUL_API_TOKEN}`,
},
fetch,
});
const gatewaySchema = stitchSchemas({
subschemas: [{ schema: shopifySchema }, { schema: contentfulSchema }],
});
const server = new ApolloServer({ schema: gatewaySchema });
return await server.listen();
}
startServer().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
These are my dependencies:
{
"#graphql-tools/load": "^7.3.2",
"#graphql-tools/schema": "^8.2.0",
"#graphql-tools/stitch": "^8.3.1",
"#graphql-tools/url-loader": "^7.2.0",
"apollo-server": "^3.4.0",
"dotenv": "^10.0.0",
"graphql": "^15.6.1",
"node-fetch": "^3.0.0"
}
Anyone knows what could be wrong with this?

Ok, i have found out that my url endpoints were just incorrect.
I'll leave the question open in case might be useful to someone.

Related

How to enable cors for apollo-server-lambda

Ok so I see a lot of answers for how to enable cors for apollo-express, but I haven't found one really for apollo-server-lambda.
This is the error that I'm getting from chrome:
Access to XMLHttpRequest at 'https://5j3gae3086.execute-api.us-east-2.amazonaws.com/alpha/'
from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check: The 'Access-Control-Allow-Origin' header
has a value 'https://example.com' that is not equal to the supplied origin.
I do not know how to change the value "https://example.com." Here is my code of how I'm trying to create the server:
const { ApolloServer } = require('apollo-server-lambda')
const typeDefs = require('./schema')
const resolvers = require ('./resolvers')
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
playground: {
endpoint: "/alpha/graphql",
},
});
exports.graphqlHandler = server.createHandler({
cors: {
// origin: true,
origin: "http://localhost:4200", // <-- This is not changing the header value. Do I need to do it from the frontend?
credentials: true,
},
});
What else do I need to do here?
Edit
I'm not sure if this is relevant, but here is my graphql.module.ts file. This is how I'm setting grahql in the frontend:
import { NgModule } from '#angular/core';
import { APOLLO_OPTIONS } from 'apollo-angular';
import { ApolloClientOptions, InMemoryCache } from '#apollo/client/core';
import { HttpLink } from 'apollo-angular/http';
const uri = 'https://5j3gae3086.execute-api.us-east-2.amazonaws.com/alpha/'; // <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
return {
link: httpLink.create({ uri,
// these comments are things that I tried with no luck :(
// fetchOptions: {
// mode: 'no-cors',
// },
// headers: {
// 'Access-Control-Allow-Origin': 'http://localhost:4200',
// 'Access-Control-Allow-Methods': 'POST',
// 'Access-Control-Allow-Headers': 'application/json'
// "Access-Control-Allow-Credentials" : true
// "X-CSRFToken": Cookies.get('csrftoken')
// },
}),
cache: new InMemoryCache(),
};
}
#NgModule({
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule { }
Also in case anyone is curious, I'm using AWS Api Gateway to use the lambda, but I believe I have the configuration for cors added correctly on that.
I'm at a loss with this. What do I need to change?
Following the CORS setup instructions here I can successfully use apollo-angular to return results for a simple query. No special headers etc. were needed.
https://www.apollographql.com/docs/apollo-server/deployment/lambda/
// serverless.yml
events:
- http:
path: graphql
method: post
cors: true
- http:
path: graphql
method: get
cors: true
// graphql.js
exports.graphqlHandler = server.createHandler({
cors: {
origin: '*',
credentials: true,
},
});
// graphql.module.ts
import {NgModule} from '#angular/core';
import {APOLLO_OPTIONS} from 'apollo-angular';
import {ApolloClientOptions, InMemoryCache} from '#apollo/client/core';
import {HttpLink} from 'apollo-angular/http';
const uri = 'https://xxx/dev/graphql';
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
return {
link: httpLink.create({uri}),
cache: new InMemoryCache(),
};
}
#NgModule({
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule {}
// In Angular 10
this.apollo
.watchQuery({
query: gql`
{
users {
email
}
}
`,
})
.valueChanges.subscribe(result => {
console.log(result.data);
});
Unlike an initial question, graphql.js is replaced to typescript as following.
// graphql.ts
exports.graphqlHandler = server.createHandler({
expressGetMiddlewareOptions: {
cors: {
origin: '*',
credentials: true,
},
},
});

Apollo Graphql with Angular with headers and Subscriptions

I need to add headers to my graphql requests with angular with subscriptions. but I didn't find any way. headers will be added if I only used headers without subscriptions. Also, subscriptions will works if I didn't add headers. But with both, it won't work. here is my code
import { NgModule } from '#angular/core';
import { HttpClientModule } from '#angular/common/http';
import { ApolloModule, Apollo, APOLLO_OPTIONS } from 'apollo-angular';
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { setContext } from 'apollo-link-context';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
const uri = 'http://localhost:5000/graphql';
export function provideApollo(httpLink: HttpLink) {
const basic = setContext((operation, context) => ({
headers: {
Accept: 'charset=utf-8'
}
}));
// Get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
const auth = setContext((operation, context) => ({
headers: {
Authorization: `Bearer ${token}`
},
}));
const subscriptionLink = new WebSocketLink({
uri:
'ws://localhost:5000/graphql',
options: {
reconnect: true,
connectionParams: {
authToken: localStorage.getItem('token') || null
}
}
});
const link = split(({ query }) => {
const { kind } = getMainDefinition(query);
return kind === 'OperationDefinition';
}, subscriptionLink, ApolloLink.from([basic, auth, httpLink.create({ uri })]));
// const link = ApolloLink.from([basic, auth, httpLink.create({ uri }), subscriptionLink]);
const cache = new InMemoryCache();
return {
link,
cache
};
}
#NgModule({
exports: [
HttpClientModule,
ApolloModule,
HttpLinkModule
],
providers: [{
provide: APOLLO_OPTIONS,
useFactory: provideApollo,
deps: [HttpLink]
}]
})
export class GraphQLModule { }
in here headers will not be added. Any Solutions?
This is the solution that I found.
Import below code to your app.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { HttpHeaders } from '#angular/common/http';
import { APOLLO_OPTIONS } from "apollo-angular";
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { split } from 'apollo-link';
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from 'apollo-angular-link-http';
#NgModule({
imports: [
CommonModule,
],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory(httpLink: HttpLink) {
const http = httpLink.create({
uri: 'http://localhost/v1/graphql',
headers: new HttpHeaders({
"x-hasura-admin-secret": "mysecretkey"
})
})
const ws = new WebSocketLink({
uri: `ws://localhost/v1/graphql`,
options: {
reconnect: true,
connectionParams: {
headers: {
"x-hasura-admin-secret": "mysecretkey"
}
}
}
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
ws,
http,
);
return {
link,
cache: new InMemoryCache(),
};
},
deps: [HttpLink],
},
]
})
export class GraphqlModule { }
import { NgModule } from "#angular/core";
import { HttpClientModule, HttpHeaders } from "#angular/common/http";
import { ApolloModule, Apollo, APOLLO_OPTIONS } from "apollo-angular";
import { HttpLinkModule, HttpLink } from "apollo-angular-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloLink, split, from } from "apollo-link";
import { setContext } from "apollo-link-context";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import ApolloClient from "apollo-client";
const uri = "http://localhost:5000/graphql";
const subscriptionLink = new WebSocketLink({
uri: "ws://localhost:5000/graphql",
options: {
reconnect: true,
connectionParams: {
authToken: localStorage.getItem("token") || null,
},
},
});
const authMiddleware = new ApolloLink((operation: any, forward: any) => {
operation.setContext({
headers: new HttpHeaders().set(
"Authorization",
`Bearer ${localStorage.getItem("token")}` || null,
),
});
return forward(operation);
});
export function createApollo(httpLink: HttpLink) {
return {
link: from([
authMiddleware,
split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
subscriptionLink,
httpLink.create({
uri: "http://localhost:5000/graphql",
}),
),
]),
cache: new InMemoryCache(),
};
}
#NgModule({
exports: [HttpClientModule, ApolloModule, HttpLinkModule],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule {}

Proper way to setup AWSAppSyncClient, Apollo & React

I've been having issues getting started with React, Apollo, and AWS-AppSync. I can't resolve this error message:
TypeError: this.currentObservable.query.getCurrentResult is not a function
I'm using the updated packages of #apollo/react-hooks and aws-appsync.
My current setup looks like this.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import config from './aws-exports';
import AWSAppSyncClient from 'aws-appsync';
import { ApolloProvider } from '#apollo/react-hooks';
const client = new AWSAppSyncClient({
url: config.aws_appsync_graphqlEndpoint,
region: config.aws_appsync_region,
auth: {
type: config.aws_appsync_authenticationType,
apiKey: config.aws_appsync_apiKey
}
});
ReactDOM.render(
<ApolloProvider client={client}>
<React.StrictMode>
<App />
</React.StrictMode>
</ApolloProvider>,
document.getElementById('root')
);
And I have a function that makes a query that looks like this:
import React from 'react';
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
const Flavors = () => {
const GET_FLAVORS = gql`
query listAll {
items {
name,
image
}
}
`;
const { loading, error, data } = useQuery(GET_FLAVORS);
if(loading) return <p>loading...</p>
if(error) return <p>Something went wrong...</p>
return (
<div>
{
data.listIceCreamTypes.items.map(type => {
return <div key={type.name}>
<img src={type.image} alt={type.name} />
<h1>{type.name}</h1>
</div>
})
}
</div>
)
}
export default Flavors;
I've gone through various solutions described in https://github.com/apollographql/react-apollo/issues/3148 such as adding:
"resolutions": {
"apollo-client": "2.6.3"
}
to package.json. Then re-running npm install and restarting the server.
Nothing seems to solve my issues.
Edit** Here's a repo to reproduce the problem: https://github.com/Rynebenson/IceCreamQL
I already answered this on other related question here on stackoverflow..
As mentioned in other answer the problem is because aws-appsync is relying in an previous version apollo-client. Using resolutions is not the "cleaner" way to solve this problem as you're fixing a dependency version which is not fully compatible with this library.
I strongly recommend you to create a custom apollo client for AWS AppSync this way:
import { ApolloProvider } from '#apollo/react-hooks';
import { ApolloLink } from 'apollo-link';
import { createAuthLink } from 'aws-appsync-auth-link';
import { createHttpLink } from 'apollo-link-http';
import { AppSyncConfig } from './aws-exports';
import ApolloClient from 'apollo-client';
const url = AppSyncConfig.graphqlEndpoint;
const region = AppSyncConfig.region;
const auth = {
type: AppSyncConfig.authenticationType,
apiKey: AppSyncConfig.apiKey
};
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createHttpLink({ uri: url })
]);
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
const WithProvider = () => (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
)
export default WithProvider
I also created a more detailed post on medium
Wanted to post my experience that is a more recent. The accepted answer is still correct. aws-appsync uses outdated packages which conflict with the newest versions of the apollo client.
import React from 'react';
import PropTypes from 'prop-types';
// Apollo Settings
import { ApolloClient, ApolloProvider, InMemoryCache } from '#apollo/client';
import { ApolloLink } from 'apollo-link';
// AppSync
import { Auth } from 'aws-amplify';
import { createAuthLink } from 'aws-appsync-auth-link';
import { createHttpLink } from 'apollo-link-http';
import awsconfig from 'assets/lib/aws-exports';
// jwtToken is used for AWS Cognito.
const client = new ApolloClient({
link: ApolloLink.from([
createAuthLink({
url: awsconfig.aws_appsync_graphqlEndpoint,
region: awsconfig.aws_appsync_region,
auth: {
type: awsconfig.aws_appsync_authenticationType,
apiKey: awsconfig.aws_appsync_apiKey,
jwtToken: async () => (await Auth.currentSession()).getAccessToken().getJwtToken(),
},
}),
createHttpLink({ uri: awsconfig.aws_appsync_graphqlEndpoint }),
]),
cache: new InMemoryCache(),
});
const withApollo = ({ children}) => {
return <ApolloProvider client={client}><App /></ApolloProvider>;
};
withApollo.propTypes = {
children: PropTypes.object,
authData: PropTypes.object,
};
export default withApollo;
Package.json
"#apollo/client": "^3.3.15",
"#apollo/react-hooks": "^4.0.0",
"apollo-link": "^1.2.14",
"apollo-link-http": "^1.5.17",
"aws-amplify": "^3.3.27",
"aws-amplify-react-native": "^4.3.2",
"aws-appsync-auth-link": "^3.0.4",
You must initialize appsync like the following method.
const graphqlClient = new appsync.AWSAppSyncClient({
url: graphqlEndpoint,
region: process.env.AWS_REGION,
auth: {
type: 'AWS_IAM',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
sessionToken: process.env.AWS_SESSION_TOKEN
}
},
disableOffline: true
});

subscriptionsClient.subscribe is not a function

At this instance i am just trying to run a dummy subscription using graphql-subscriptions, and later-on, integrate it in my code.
However, even with the minimal example.
I am following a scotch.io example, here is its git link
https://github.com/kimobrian/GraphQL-Express/tree/subscriptions
it is throwing error on subscription at graphiql
subscriptionsClient.subscribe is not a function
Note: I am not even trying it from a separate client. I just want a server that has schema including queries and mutations and subscription, and i want to be able to see the real-time magic of subscription when i run it in graphiql in different windows.
Following some suggestions on the net, i have even downgraded subscription-transport-ws version from 0.9 to 0.8.3
I have running queries and mutations but subscription throws above mentioned error in Graphiql
Server File
import express from 'express';
import {
graphqlExpress,
graphiqlExpress,
} from 'graphql-server-express';
import bodyParser from 'body-parser';
import cors from 'cors';
import { schema } from './src/schema';
import { execute, subscribe } from 'graphql';
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
const PORT = 7900;
const server = express();
server.use('*', cors({ origin: 'http://localhost:3000' }));
server.use('/graphql', bodyParser.json(), graphqlExpress({
schema
}));
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: `ws://localhost:${PORT}/subscriptions`
}));
// We wrap the express server so that we can attach the WebSocket for subscriptions
const ws = createServer(server);
ws.listen(PORT, () => {
console.log(`GraphQL Server is now running on http://localhost:${PORT}`);
// Set up the WebSocket for handling GraphQL subscriptions
new SubscriptionServer({
execute,
subscribe,
schema
}, {
server: ws,
path: '/subscriptions',
});
});
package.json
{
"name": "tutorial-server",
"version": "1.0.0",
"description": "A simple GraphQL server",
"main": "server.js",
"scripts": {
"start": "nodemon ./server.js --exec babel-node -e js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.24.0",
"babel-preset-es2015": "^6.24.0",
"babel-preset-stage-0": "^6.22.0",
"nodemon": "^1.11.0"
},
"dependencies": {
"body-parser": "^1.17.1",
"cors": "^2.8.3",
"express": "^4.15.2",
"graphql": "^0.11.3",
"graphql-server-express": "^1.1.2",
"graphql-subscriptions": "^0.5.1",
"graphql-tools": "^1.2.3",
"subscriptions-transport-ws": "0.8.3"
}
}
Schema
import {
makeExecutableSchema
} from 'graphql-tools';
import { resolvers } from './resolvers';
const typeDefs = `
type Channel {
id: ID! # "!" denotes a required field
name: String
}
type Message {
id: ID!
text: String
}
# This type specifies the entry points into our API
type Query {
channels: [Channel] # "[]" means this is a list of channels
channel(id: ID!): Channel
}
# The mutation root type, used to define all mutations
type Mutation {
addChannel(name: String!): Channel
}
# The subscription root type, specifying what we can subscribe to
type Subscription {
channelAdded: Channel
}
`;
const schema = makeExecutableSchema({ typeDefs, resolvers });
export { schema };
Resolver
import { PubSub } from 'graphql-subscriptions';
const channels = [{
id: 1,
name: 'soccer',
}, {
id: 2,
name: 'baseball',
}];
let nextId = 3;
const CHANNEL_ADDED_TOPIC = 'newChannel';
const pubsub = new PubSub();
export const resolvers = {
Query: {
channels: () => {
return channels;
},
channel: (root, { id }) => {
return channels.find(channel => channel.id === id);
},
},
Mutation: {
addChannel: (root, args) => {
const newChannel = { id: String(nextId++), messages: [], name: args.name };
channels.push(newChannel);
pubsub.publish(CHANNEL_ADDED_TOPIC, { channelAdded: newChannel });
return newChannel;
}
},
Subscription: {
channelAdded: {
subscribe: () => pubsub.asyncIterator(CHANNEL_ADDED_TOPIC)
}
}
};
I expect that it should update in the subscription window when pubsub.publish is hit

Apollo client link state "Missing field in {}" when writing to cache?

Im using Apollo Client, Graphcool and React. I have a working login form but I need the UI to update when the user is logged in, and I need this to happen in different components.
It seems apollo-link-state is the solution for this. My code below seems to work but Im getting this error:
Missing field CurrentUserIsLoggedIn in {} in writeToStore.js
My Apollo Client setup:
import React from 'react';
import ReactDOM from 'react-dom';
// Apollo
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { withClientState } from 'apollo-link-state';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
// Components
import LoginTest from './components/App/LoginTest';
const wsLink = new WebSocketLink({
uri: `wss://subscriptions.graph.cool/v1/XXX`,
options: {
reconnect: true,
},
});
// __SIMPLE_API_ENDPOINT__ looks like: 'https://api.graph.cool/simple/v1/__SERVICE_ID__'
const httpLink = new HttpLink({
uri: 'https://api.graph.cool/simple/v1/XXX',
});
// auth
const middlewareAuthLink = new ApolloLink((operation, forward) => {
const token = localStorage.getItem('auth-token');
const authorizationHeader = token ? `Bearer ${token}` : null;
operation.setContext({
headers: {
authorization: authorizationHeader,
},
});
return forward(operation);
});
const cache = new InMemoryCache();
const defaultState = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: false,
},
};
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
CurrentUserIsLoggedIn: (_, args) => {
const data = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: args.value,
},
};
cache.writeData({ data });
},
},
},
});
const client = new ApolloClient({
cache,
link: ApolloLink.from([
stateLink,
middlewareAuthLink,
split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
),
]),
});
ReactDOM.render(
<ApolloProvider client={client}>
<LoginTest />
</ApolloProvider>,
document.getElementById('root'),
);
LoginTest.js:
import React from 'react';
import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
import App from './App';
const LoginTest = props => {
if (props.LoginServerQuery.loading) return <p>Loading...</p>;
// If the server tells us the user is logged in
if (props.LoginServerQuery.loggedInUser) {
// Then set the local logged in state to true
props.CurrentUserIsLoggedInMutation({
variables: {
value: true,
},
});
}
return <App />;
};
const CurrentUserIsLoggedInMutation = gql`
mutation CurrentUserIsLoggedInMutation($value: Boolean) {
CurrentUserIsLoggedIn(value: $value) #client {
value
}
}
`;
const LoginServerQuery = gql`
query LoginServerQuery {
loggedInUser {
id
}
}
`;
const LoginTestQuery = compose(
graphql(LoginServerQuery, { name: 'LoginServerQuery' }),
graphql(CurrentUserIsLoggedInMutation, {
name: 'CurrentUserIsLoggedInMutation',
}),
)(LoginTest);
export default LoginTestQuery;
At the moment, apollo-link-state requires you to return any result in your resolver function. It can be null too. This might be changed in the future.
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
CurrentUserIsLoggedIn: (_, args) => {
const data = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: args.value,
},
};
cache.writeData({ data });
return data;
},
},
},
try adding a return statement in your mutation. Similar problem occured here with different function: apollo-link-state cache.writedata results in Missing field warning

Resources