I am creating an application in react with graphql for the API and rails as the backend. I have just configured the environment variables so that depending on whether the application is running in production or in development the url that is consumed in the front changes.
But when I deploy in heroku and check the console in the browser, the url of the api is still the development one http://localhost:3000. I don't know why React is not recognizing the production environment in heroku.
I just created a file constant.js:
const prod = {
url: {
API_URL: 'https://hackathon-one-api.herokuapp.com/graphql'
}
}
const dev = {
url: {
API_URL: 'http://localhost:3000/graphql'
}
}
console.log(process.env.NODE_ENV)
export const config = process.env.NODE_ENV === 'development' ? dev : prod;
And in my index.js (where is imported):
// Environments variables
import { config } from '../src/constant';
// 1
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
// 2
const httpLink = createHttpLink({
uri: config.url.API_URL //here!
});
// 3
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
// 4
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
Related
I get issues when using #apollo/client: 3.5.10, aws-appsync:4.1.5.
There is my config
import { ApolloClient, ApolloLink, createHttpLink, InMemoryCache } from '#apollo/client';
import { createAuthLink } from 'aws-appsync-auth-link';
import { createSubscriptionHandshakeLink } from 'aws-appsync-subscription-link';
// Config
import { AWS_APPSYNC } from '../config';
const { graphqlEndpoint, region, apiKey } = AWS_APPSYNC;
const auth = {
type: AWS_APPSYNC.authenticationType,
apiKey: apiKey,
};
const httpLink = createHttpLink({ uri: graphqlEndpoint });
const link = ApolloLink.from([
// #ts-ignore
createAuthLink({ graphqlEndpoint, region, auth }),
// #ts-ignore
createSubscriptionHandshakeLink({ graphqlEndpoint, region, auth }, httpLink),
]);
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
and I am using
const {
data: subscription_message_data,
loading: subscription_message_loading,
error: subscription_message_error
} = useSubscription(
SUBSCRIPTION_NEW_MESSAGE, {
variables: { conversationId: conversationId }
});
But I got an error form useSubscription is: "Subscribe only available for AWS AppSync endpoint"
Does anyone have experience with this issue?
Had the exact same issue, and was baffled by this error message.
At the end the issue turned out to be the incorrect property name for the url used in the createAuthLink and createSubscriptionHandshakeLink input objects.
This was the fix that worked for me:
const link = ApolloLink.from([
createAuthLink({ url: graphQLEndpoint, region, auth }),
createSubscriptionHandshakeLink({ url: graphQLEndpoint, region, auth }, httpLink),
]);
So I'm trying to do a custom header for the requests to my graphql backend from my nextJS frontend. The code of the graphql client is as follows:
import { ApolloClient, ApolloLink, InMemoryCache, createHttpLink } from '#apollo/client'
import { setContext } from '#apollo/client/link/context'
import getConfig from 'next/config'
import nookies, { parseCookies } from 'nookies'
const { publicRuntimeConfig } = getConfig()
const httpLink = createHttpLink({
uri: publicRuntimeConfig.uri,
credentials: 'same-origin'
})
const authLink = setContext((_, { headers }) => {
let token = ''
if (typeof window !== 'undefined') {
const { 'toDo-token': newToken } = parseCookies()
token = newToken
} else {
// error is right here. I cant find a way to access the ctx
const { 'toDo-token': newToken } = nookies.get(ctx)
token = newToken
}
return {
headers: {
...headers,
Authorization: token ?? undefined
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
ssrMode: typeof window === 'undefined'
})
export default client
On the client side it works just fine. However, since I cant access client cookies on the server side, I'm trying to use the get function from nookies to grab the auth cookies and pass to the headers with the setContext function. The problem I'm having is that I cant find a way to grab the app context from next so I can look at the request info and grab the ctx from there so I can see the cookies.
I do something similar in my _app.tsx in the following way:
MyApp.getInitialProps = async ({ ctx }: AppContext) => {
const { locale } = ctx
const { 'toDo-token': token } = nookies.get(ctx)
let user = null
try {
user = await useUser()
} catch {
user = null
}
return {
locale: locale === 'default' ? 'en' : locale as LocaleEnum,
token: token ?? null,
user
}
}
Does anyone know how I can access the request context in the graphql setContext function?
Decided to switch from normal React to NextJS after watching various videos and reading articles. I'm currently trying to implement Apollo Client but am getting this (title) error and was hoping to get some help. The way my withData is currently set is
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { hasSubscription } from '#jumpn/utils-graphql';
import * as AbsintheSocket from '#absinthe/socket';
import withApollo from 'next-with-apollo';
import { createAbsintheSocketLink } from '#absinthe/socket-apollo-link';
import { Socket as PhoenixSocket } from 'phoenix';
let apolloClient = null;
const HTTP_ENDPOINT = 'http://localhost:4000/api/v1/graphiql';
const WS_ENDPOINT = 'ws://localhost:4000/api/v1/socket';
const httpLink = createHttpLink({
url: HTTP_ENDPOINT
});
const socketLink = createAbsintheSocketLink(AbsintheSocket.create(new PhoenixSocket(WS_ENDPOINT)));
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('auth-item');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
};
});
const link = new ApolloLink.split(
(operation) => hasSubscription(operation.query),
socketLink,
authLink.concat(httpLink)
);
const create = (initialState) => {
return new ApolloClient({
link: link,
cache: new InMemoryCache().restore(initialState || {})
});
};
const initApollo = (initialState) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return create(initialState);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState);
}
return apolloClient;
};
export default withApollo(initApollo);
All help is appreciated to understand what I did wrong and what is a better approach should there be one.
The issue is because nextJs will run the code above in the server and WebSocket is a property that exists only in the browser, to get this fixed you can do:
const socketLink =
process.browser &&
createAbsintheSocketLink(
AbsintheSocket.create(
new PhoenixSocket(WS_URI)
)
)
By checking for process.browser you make sure that this function is executed only on client-side.
I'm using GraphQLServer from graphql-yoga to handle requests. My back-end is able to communicate with my React front-end at this point and I can make graphql queries and get the response just fine.
I was recently informed that I should be setting a cookie with the token, rather than returning it in the mutation response. So I'm trying to switch over but the cookie isn't being set by the mutation.
server.js (node)
import { GraphQLServer, PubSub } from 'graphql-yoga';
import {resolvers, fragmentReplacements} from './resolvers/index'
import prisma from './prisma'
const pubsub = new PubSub()
export default new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
context(request) {
return {
pubsub,
prisma,
request, //fragmentReplacements[], request, response
}
},
fragmentReplacements
});
Mutation.js (node)
export default {
async createUser(parent, args, {prisma, request}, info) {
const lastActive = new Date().toISOString()
const user = await prisma.mutation.createUser({ data: {...args.data, lastActive }})
const token = generateToken(user.id)
const options = {
maxAge: 1000 * 60 * 60 * 24, //expires in a day
// httpOnly: true, // cookie is only accessible by the server
// secure: process.env.NODE_ENV === 'prod', // only transferred over https
// sameSite: true, // only sent for requests to the same FQDN as the domain in the cookie
}
const cookie = request.response.cookie('token', token, options)
console.log(cookie)
return {user}
},
// more mutations...
console.log(cookie) outputs with the cookie attached
index.js (react)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import ApolloClient, { InMemoryCache, create } from 'apollo-boost';
import {ApolloProvider} from 'react-apollo'
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache: new InMemoryCache(),
credentials: 'include',
request: async operation => {
operation.setContext({
fetchOptions: {
credentials: 'same-origin'
}
})
},
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root'));
So my questions are:
Is there a better way to do authentication with GraphQL, or is setting the token with a cookie in the auth mutation suitable?
Assuming it's a decent approach, how can I set the cookie from the mutation?
Thanks for your time!
You need to include the credentials in the ApolloClient
fetchOptions: {
credentials: 'include'
}
I would like to set up a graphql client with React for both uploading file and handle subscriptions from a graphql server.
The file upload and the other queries work well. The problem is with subscriptions. I get in the browser console the following error:
WebSocket connection to 'ws://localhost:3001/subscriptions' failed: Connection closed before receiving a handshake response
I have used apollo-upload-client for file upload and apollo-link-ws for subscriptions.
I can see that subscriptions-transport-ws suggests using createNetworkInterface and addGraphQLSubscriptions but this approach is not compatible with apollo-upload-client that only supports createUploadLink.
I'm stuck. Please help.
I setup my client like this:
import React from 'react';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { createUploadLink } from 'apollo-upload-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, Observable, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
const cache = new InMemoryCache();
const request = async (operation) => {
const token = localStorage.getItem('token');
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : '',
},
});
};
const httpLink = createUploadLink({ uri: 'http://localhost:3001/graphql' });
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: 'ws://localhost:3001/subscriptions',
options: {
reconnect: true
},
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
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 apolloClient = new ApolloClient({
link: ApolloLink.from([
requestLink,
link,
]),
cache,
});
export const withApolloClient = App => (
<ApolloProvider client={apolloClient}>
<App client={apolloClient} />
</ApolloProvider>
);
export default apolloClient;
I am using a similar config but instead of importing WebSocketLink from apollo-link-ws I imported it from #apollo/client.
With that setup i had both the subscription and upload working.
import { WebSocketLink } from "#apollo/client/link/ws";
I would suggest to use graphql-server-express like this