Apollo studio CORS error unable to reach server - graphql

I'm trying to setup apollo server in my NextJS project, I'm using apollo-server-micro and I ran into these issues:
the apollo studio sandbox is unable to reach the server due to CORS.
This is in pages/api/graphql
import { ApolloServer } from "apollo-server-micro";
import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers/index";
import { createContext } from "./db/context";
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ res, req }) => createContext(res, req),
uploads: false,
introspection: true,
formatError: (error) => {
console.log(error);
return error;
},
});
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
module.exports = apolloServer
.start()
.then(() => apolloServer.createHandler({ path: "/api/graphql" }));
then I tried to enable cors but without success because I get this error:
API resolved without sending a response for /api/graphql, this may result in stalled requests.
import { ApolloServer } from "apollo-server-micro";
import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers/index";
import { createContext } from "./db/context";
import { send } from "micro";
import cors from "micro-cors";
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ res, req }) => createContext(res, req),
uploads: false,
introspection: true,
formatError: (error) => {
console.log(error);
return error;
},
});
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
module.exports = apolloServer.start().then(() => {
const handler = apolloServer.createHandler({ path: "/api/graphql" });
return cors((req, res) => {
console.log(res);
return req.method === "OPTIONS" ? send(res, 200, 'ok') : handler(req, res);
});
});

Related

req.headers.authorization coming back as undefined

I'm pretty new to graphql and apollo, and I'm working on a project where I need to be able to use context to get a variable for my query. The issue that I'm having is that my context.user is coming back null because my req.headers.authorization is undefined. I'm unsure as to why this is happening as in my frontend
const authLink = setContext(async (_, { headers }) => {
const token = await AsyncStorage.getItem('token');
try {
if (token !== null) {
return {
headers: {
...headers,
authorization: `Bearer ${token}` || null,
}
}
}
}
catch (error) {
throw error;
}
});
my token is not null and when I tested const auth: `Bearer ${token}` || null console.log(auth) after if (token !== null) { it came back with Bearer and my token value. Does anyone know why this is happening? I would really appreciate any help or advice. Thank you!
rest of frontend client.js
import { ApolloClient, split, createHttpLink, HttpLink, InMemoryCache } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { setContext } from "#apollo/client/link/context";
import AsyncStorage from '#react-native-async-storage/async-storage';
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const wslink = new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/subscriptions",
/* connectionParams: {
authToken: user.authToken,
},*/
}),
);
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const authLink = setContext(async (_, { headers }) => {
const token = await AsyncStorage.getItem('token');
try {
if (token !== null) {
return {
headers: {
...headers,
authorization: `Bearer ${token}` || null,
}
}
}
}
catch (error) {
throw error;
}
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wslink,
authLink.concat(httpLink)
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
backend index.js
import express from 'express';
import mongoose from 'mongoose';
import WebSocket from 'ws';
import { createServer } from 'http';
import { ApolloServer } from 'apollo-server-express';
import { makeExecutableSchema } from '#graphql-tools/schema';
import {
ApolloServerPluginDrainHttpServer,
ApolloServerPluginLandingPageLocalDefault,
} from "apollo-server-core";
import { useServer } from 'graphql-ws/lib/use/ws';
import constants from './config/constants.js';
import typeDefs from './graphql/schema.js';
import resolvers from './graphql/resolvers/index.js';
import { decodeToken } from './services/auth.js';
const app = express();
const httpServer = createServer(app);
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
context: ({ req }) => ({
user: req.user
}),
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
const wsServer = new WebSocket.Server({
server: httpServer,
path: '/graphql',
});
const serverCleanup = useServer({ schema }, wsServer);
async function auth(req, res, next) {
try {
const token = req.headers.authorization;
//token is undefined Why???
if (token != null) {
const user = await decodeToken(token);
req.user = user; // eslint-disable-line
}
else {
req.user = null; // eslint-disable-line
}
next();
} catch (error) {
throw error;
}
}
app.use(auth);
await server.start();
server.applyMiddleware({ app });
mongoose
.connect(process.env.MONGODB_URL || 'mongodb://localhost/AMO', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log("MongoDB Connected");
return httpServer.listen(4000, () => {
console.log(`Server ready at http://localhost:4000/graphql`);
});
})

GraphQL subscription isn't triggered after PubSub publish

I'm trying to create a basic subscriptions system using WebSockets, TypeGraphQL and Apollo Server. I've created the WS server and it seems to work correctly.
import express from 'express'
import cors from 'cors'
import cookieParser from 'cookie-parser'
import { ApolloServer } from 'apollo-server-express'
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core'
import { buildSchema } from 'type-graphql'
import { MessageResolver } from './resolvers/messageResolver'
import { appDataSource } from './dataSource'
import { PubSub } from 'graphql-subscriptions'
import http from 'http'
import { WebSocketServer } from 'ws'
const main = async () => {
appDataSource.initialize().then(() => {
console.log("DataSource initialized.")
})
const app = express()
app.set('trust proxy', 1)
app.use(
cors({
origin: process.env.CORS_ORIGIN,
credentials: true,
})
)
app.use(cookieParser())
const schema = await buildSchema({
resolvers: [MessageResolver],
validate: false
})
const pubsub = new PubSub();
const apolloServer = new ApolloServer({
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground(),
],
schema,
context: ({ req, res }) => ({ req, res, pubsub }),
})
await apolloServer.start()
apolloServer.applyMiddleware({ app, cors: false })
const httpServer = http.createServer()
const wss = new WebSocketServer({
server: httpServer
})
httpServer.on('request', app);
httpServer.listen(5000, function () {
console.log(`http/ws server listening on 5000`);
});
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log(`received: ${message}`);
ws.send(JSON.stringify({
answer: 42
}));
});
});
}
main().catch((err) => {
console.error(err.stack);
})
Here are my subscription and my mutation:
#Subscription({
topics: "NOTIFICATIONS"
})
test(
#Root() message: string,
): string {
return message
}
#Mutation(() => Boolean)
async addNewComment(
#Arg("message") message: string,
#PubSub("NOTIFICATIONS") publish: Publisher<string>
) {
await Message.create({ message }).save()
await publish(message);
return true;
}
The problem I have is that when I use the mutation, the subscription should be triggered. This thing doesn't happen and I don't know why. Do you have any ideas?
Thank you!

Using passport-google-oauth2 authentication in server - client app

I have a TypeOrm/React app connected with mongodb database, where server is on port 4000, and client is on port 3000.
On client I created file googleAuth:
import passport from 'passport';
import {User} from './entity/User';
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const googleOptions = {
clientID: process.env.clientID,
clientSecret: process.env.clientSecret,
callbackURL: 'http://localhost:4000/auth/google/callback',
profileFields: ['id', 'email', 'first_name', 'last_name'],
};
const googleCallback = (_accessToken: any, _refreshToken: any, profile: any, done: any) => {
const matchingUser = User.findOne({googleId: profile.id});
if (matchingUser) {
done(null, matchingUser);
return;
}
const newUser = {
googleId: profile.id,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
email: profile.emails && profile.emails[0] && profile.emails[0].value
};
User.create(newUser);
done(null, newUser);
}
export const googleAuth = () => passport.use(new GoogleStrategy(
googleOptions,
googleCallback
));
My index.ts(server) file looks like this:
import "reflect-metadata";
import {createConnection} from "typeorm";
import "dotenv/config"
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { buildSchema } from "type-graphql";
import cors from "cors";
import { PostResolver } from "./resolvers/PostResolver";
import { UserResolver } from "./resolvers/UserResolver";
import path from 'path';
import passport from "passport";
import { googleAuth} from './googleAuth';
(async () => {
await createConnection();
googleAuth();
passport.serializeUser(function(user: any, done: any) {
done(null, user);
});
passport.deserializeUser(function(user: any, done: any) {
done(null, user);
});
const app = express();
app.use(cors());
app.use(passport.initialize());
app.get('/auth/google', passport.authenticate('google', { scope: ['email'] }));
app.get('/auth/google/callback', passport.authenticate('google', {
successRedirect: 'http://localhost:4000/graphql',
failureRedirect: 'http://localhost:4000/graphql',
}));
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [ PostResolver, UserResolver ]
}),
context: ({ req }) => ({
getUser: () => req.user,
logout: () => req.logout()
})
// context: ({ req, res }) => ({ req, res })
});
apolloServer.applyMiddleware({ app });
app.use(express.static('public'));
app.get('*', (_, res) => {
res.sendFile(path.resolve(__dirname, '../public', 'index.html'))
});
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`Server started on port ${port}`));
})();
My UserResolver:
import {
Resolver,
Query,
Ctx
} from "type-graphql";
import { User } from "../entity/User";
import { MyContext } from '../MyContext';
import { ExpressContext } from "apollo-server-express/dist/ApolloServer";
#Resolver()
export class UserResolver {
#Query(() => [User])
users() {
return User.find();
}
#Query(() => String)
getUser(#Ctx() { req }: ExpressContext) {
console.log(req);
return `your user id is: ${req.user}`;
}
}
In https://console.developers.google.com in credenatials I add
Authorised JavaScript origins: http://localhost:4000
In Authorised redirect URIs: http://localhost:4000/auth/google/callback
I can log in, but I can't get any data. I know that it's very messy code, but I need to organize this and rewrite it.
First, I want to know what domains and redirect should I add in my credentials.
Second is to configure server.
Third to create UserResolvers.
Fourth to get data with client.

Configuring subscriptions with NEXT.js and Apollo client 2

I am trying to configure subscriptions with Apollo 2 and NEXT.js. I can get the client to connect to the server and they are working in the GraphQL playground, so the bad configuration must be in the withData file, or the component that handles the subscription.
When inspecting the socket connection on the network panel in chrome, the subscription payload does not get added as a frame, like it does in the GraphQL playground.
withData:
import { ApolloLink, Observable } from 'apollo-link';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { onError } from 'apollo-link-error';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log({ graphQLErrors });
}
if (networkError) {
console.log('Logout user');
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
createPersistedQueryLink().concat(
new BatchHttpLink({
uri: GRAPHQL_ENDPOINT,
credentials: 'include'
}),
process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: null
)
]),
cache
});
}
export default withApollo(createClient);
Subscription component:
import { CONVERSATION_QUERY } from '../../constants/queries';
import { CONVERSATION_SUBSCRIPTION } from '../../constants/subscriptions';
import PropTypes from 'prop-types';
import { Query } from 'react-apollo';
const Conversation = props => (
<Query
{...props}
query={CONVERSATION_QUERY}
variables={{ input: { _id: props._id } }}
>
{(payload) => {
const more = () => payload.subscribeToMore({
document: CONVERSATION_SUBSCRIPTION,
variables: { input: { conversation: props._id } },
updateQuery: (prev, { subscriptionData }) => {
console.log({ subscriptionData });
if (!subscriptionData.data.messageSent) return prev;
const data = subscriptionData;
console.log({ data });
return Object.assign({}, prev, {});
},
onError(error) {
console.log(error);
},
onSubscriptionData: (data) => {
console.log('onSubscriptionData ', data);
}
});
return props.children({ ...payload, more });
}}
</Query>
);
Conversation.propTypes = {
children: PropTypes.func.isRequired
};
export default Conversation;
The subscription that has been tested in the GraphQL playground:
import gql from 'graphql-tag';
export const CONVERSATION_SUBSCRIPTION = gql`
subscription messageSent($input: messageSentInput) {
messageSent(input: $input) {
_id
users {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
messages {
_id
body
createdAt
read
sender {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
}
}
}
`;
The more function is then executed in componentDidMount:
componentDidMount() {
this.props.subscribeToMore();
}
The result in the console from the log in updateQuery is:
{"data":{"messageSent":null}}
I hadn't configured my withData file properly. You need to use split from the apollo-link package to let Apollo determine if the request should be handled with http or ws. Here is my working configuration file.
import { ApolloLink, Observable } from 'apollo-link';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { getMainDefinition } from 'apollo-utilities';
import { onError } from 'apollo-link-error';
import { split } from 'apollo-link';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const httpLink = new BatchHttpLink({
uri: GRAPHQL_ENDPOINT
});
// Make sure the wsLink is only created on the browser. The server doesn't have a native implemention for websockets
const wsLink = process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: () => {
console.log('SSR');
};
// Let Apollo figure out if the request is over ws or http
const terminatingLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return (
kind === 'OperationDefinition'
&& operation === 'subscription'
&& process.browser
);
},
wsLink,
httpLink
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.error({ graphQLErrors });
}
if (networkError) {
console.error({ networkError});
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
// Push the links into the Apollo client
createPersistedQueryLink().concat(
// New config
terminatingLink
// Old config
// new BatchHttpLink({
// uri: GRAPHQL_ENDPOINT,
// credentials: 'include'
// })
)
]),
cache
});
}
export default withApollo(createClient);

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