Can't upload a file to Strapi using GraphQL and Apollo - reactjs

I have a React/Next.js app which is using GraphQL and Apollo to connect and interact with a headless API. I am using Strapi as my headless API/CMS which is working great, except for one issue. I am trying to upload a file from my React app to a content type in my Strapi CMS using a GraphQL mutation and it keeps failing on the upload part.
When I upload a file using Altair(my playground environment) to Strapi with the exact same mutation everything works fine, but once I try to run the same mutation from my React app I get this error:
Variable \"$file\" got invalid value {}; Upload value invalid.. Everything I see online brings up using apollo-upload-client and adding something like link: createUploadLink({ uri: "http://localhost:4300/graphql" }), to my Apollo client initiation. I have tried that but whenever I use it, it breaks my app and I get this GraphQL error Error: Cannot return null for non-nullable field UsersPermissionsMe.username..
It seems like the only answer I can find is using apollo-upload-client but when I use it, it seems to break my app. I don't know if I need to use it differently maybe because I am using Strapi, or Next, or #apollo/client. I am a little lost on this one.
This is how I initiate my Apollo client and when everything works except uploading a file.
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from "#apollo/client";
import { parseCookies } from "nookies";
import { useMemo } from 'react'
import getConfig from "next/config";
const { publicRuntimeConfig } = getConfig();
let apolloClient: ApolloClient<NormalizedCacheObject>;
function createApolloClient(ctx) {
return new ApolloClient({
ssrMode: typeof window === "undefined",
uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${ctx ? parseCookies(ctx).jwt : parseCookies().jwt}`,
},
});
}
My app runs perfect with this code except for the fact that I get "message":"Variable \"$file\" got invalid value {}; Upload value invalid.", whenever I try to upload a file. So when I try to use apollo-upload-client to fix that like this:
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from "#apollo/client";
import { createUploadLink } from 'apollo-upload-client';
import { parseCookies } from "nookies";
import { useMemo } from 'react'
import getConfig from "next/config";
const { publicRuntimeConfig } = getConfig();
let apolloClient: ApolloClient<NormalizedCacheObject>;
function createApolloClient(ctx) {
return new ApolloClient({
ssrMode: typeof window === "undefined",
// uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL,
link: createUploadLink({ uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL }),
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${ctx ? parseCookies(ctx).jwt : parseCookies().jwt}`,
},
});
}
I get this error: Error: Cannot return null for non-nullable field UsersPermissionsMe.username..
I am still new to GraphQL and Strapi so maybe I am missing something obvious. Everything works except uploads. I can upload from my playground, just not from my app, that is where I am at.

So after two days of this issue, I figured it out after 10 minutes of reading the apollo-upload-client documentation. So much shame. All I needed to do was move my header request from the ApolloClient options to the createUploadLink options. Final fix was
function createApolloClient(ctx) {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createUploadLink({
uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL,
headers: {
Authorization: `Bearer ${ctx ? parseCookies(ctx).jwt : parseCookies().jwt}`,
},
}),
cache: new InMemoryCache(),
});
}

Related

Apollo client not working on mobile browser

Using Apollo in react that's working on computer but it's not working on mobile browser, I have seen many solutions on here and github but they are all for version 2 or less, I'm using "#apollo/client": "^3.5.8" and here's where the occurs
const getSignedUrl = async (file) => {
try {
const api = await mutationFunc({ variables: {
fileName: file[0].name,
email: currentUser.email
}})
.....
} catch (error) {
throw new Error('Unknown error')
}
}
It does not even connect to the server, I just get Failed to fetch error.
According to the deprecated solutions, it has something to do with ApolloClient constructor, so here's my index file:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
HttpLink,
createNetworkInterface
} from "#apollo/client";
// const client = new ApolloClient({
// link: new HttpLink({
// uri: Platform.select({
// ios: 'http://localhost:80/',
// android: 'http://10.0.2.2:80/',
// }),
// }),
// cache: new InMemoryCache(),
// });
// const networkInterface = createNetworkInterface('/graphql')
const client = new ApolloClient({
// uri: 'http://localhost:8000/',
link: new HttpLink({ uri: 'http://localhost:8000/' }),
cache: new InMemoryCache()
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />,
</ApolloProvider>,
document.getElementById("root")
);
EDIT
The API lives on my PC, so I tried changing the ApolloClient constructor uri to
const client = new ApolloClient({
link: new HttpLink({ uri: http://192.168.1.101:8000' }),
cache: new InMemoryCache()
});
I can also query the API directly on my phone using http://192.168.1.101:8000: Here is the query:
Here is the response from the API
and Here is the failed mutation on React side:
NOTE: I'm not uploading the image just send the name to get a pre-signed s3 url. But it's not even sending to the api.
Where does your GraphQL server run? I assume it doesn't run on the mobile device. If so, you have to specify the URL of the server in Apollo client constructor.
http://localhost:8000/ --> http://myserver:8000/
And of course your mobile device should have access to port 8000 of myserver.

How can one deploy Apollo Server with Create-React-App?

I'm trying to deploy my application to a production environment, but having some trouble wiring it all together.
I've got a create-react-app for my frontend, which is served up by a simple express/serve server. On the backend, I've got NGINX proxying successfully to my API server, which is using Apollo to serve my data. The API is running on port 4000.
The Apollo-Server is as-follows, and works fine:
import { resolve } from "path";
import dotenv from "dotenv";
const envi = process.env.NODE_ENV;
dotenv.config({ path: resolve(__dirname, `../.${envi}.env`) });
import "reflect-metadata";
import { connect } from "./mongodb/connect";
import { buildSchema } from "type-graphql";
import { ApolloServer } from "apollo-server";
import { SenateCommitteeResolver, HouseCommitteeResolver } from "./resolvers";
import { populateDatabase } from "./util";
(async () => {
// Connect to MongoDB
await connect();
console.log(`šŸ“Š Databases connected`);
const schema = await buildSchema({
resolvers: [HouseCommitteeResolver, SenateCommitteeResolver],
emitSchemaFile: resolve(__dirname, "schema.gql"),
});
// If development, set database docs
envi === "development" && (await populateDatabase());
// Launch the server!
const server = new ApolloServer({
schema,
playground: true,
});
// Server listens at URL
const { url } = await server.listen(4000);
console.log(`šŸš€ Server ready, at ${url}`);
})();
I'm trying to connect my express server to the Apollo Server, but that's where I'm running into problems. The application is supposed to connect using Apollo's Client and HTTP Link, because I'm using Apollo Client on the frontend too:
import React, { useEffect } from "react";
import { AppRouter } from "./routers";
import ReactGA from "react-ga";
import { ApolloProvider } from "#apollo/client";
import client from "./graphql/client";
import "./styles/index.scss";
function App(): React.ReactElement {
return (
<ApolloProvider client={client}>
<AppRouter />
</ApolloProvider>
);
}
export default App;
And here's the client file:
import { ApolloClient, InMemoryCache, createHttpLink } from "#apollo/client";
const httpLink = createHttpLink({ uri: process.env.REACT_APP_API as string });
const cache = new InMemoryCache();
const client = new ApolloClient({
link: httpLink,
cache,
connectToDevTools: true,
});
export default client;
However, when the user navigates to the site and the site itself tries to make a request to my backend, I'm getting a CORS error:
Access to fetch at 'https://www.cloture.app/' from origin 'https://cloture.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
What's going wrong? How can I connect Apollo's client with my Apollo Server on the backend?
Adding it here, because the suggestion requires some code.
Try adding :
const server = new ApolloServer({
schema,
playground: true,
cors: {
origin: "*" // it will allow any client to access the server, but you can add specific url also
}
});

Use ApolloClient in Gatsby with localhost / local IP uri

I am trying to use ApolloClient with a local IP as uri, but when I set it, it automatically changes it from http to https and, of course, it doesn't work locally.
I've tried 2 way of configuring Gatsby to use ApolloClient.
The first way is in gatsby-browser like so:
import React from 'react';
import 'core-js/modules/es6.set';
import 'core-js/modules/es6.map';
import 'raf/polyfill';
import Apollo from 'providers/Apollo';
export const wrapRootElement = ({ element }) => <Apollo>{element}</Apollo>;
And, the ApolloClient config:
import React from 'react';
import { ApolloProvider, ApolloClient, InMemoryCache } from '#apollo/client';
export default ({ children }) => {
const client = new ApolloClient({
uri: 'http://192.162.1.112:4000/graphql',
cache: new InMemoryCache(),
request: async operation => {
...
},
fetchOptions: {
mode: 'no-cors',
},
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
The second way is using the plugin gatsby-plugin-apollo in gatsby-config like so
{
resolve: 'gatsby-plugin-apollo',
options: {
uri: 'http://192.168.1.112:4000/graphql'
}
}
Notice both uri have http.
Also, it is either one or the other, not both. (Although I've tried with both and it the same result).
This is what I get trying to do a gql query in the network tab:
It is enforcing https and I can't test locally. How do can I make request to http using Gatsby and ApolloClient?
By the way, I set ApolloClient just like this in another project that doesn't use Gatsby (obviously not using the gatsby plugin either) and it works as expected.
There are a few confusing things in the way you are using apollo client.
There is no point in explaining something that has already been done.
You might want to check out this talk and demo by Jason Lengstorf. Here he explained how one can get started with gatsby + apollo client.
Youtube
gatsby-with-apollo

I need to set my Apollo Client Cache to its defaults

I have a React app which is using Apollo Client. I'm using apollo-link-state and apollo-cache-persist and need to reset my store to its default values when client.resetStore() is called in my app.
The docs say that when creating a client you should call client.onResetStore(stateLink.writeDefaults) and this will do the job but writeDefaults is exposed by Apollo Link State which i don't have direct access to as Iā€™m using Apollo Boost.
Here is my Apollo Client code:
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { persistCache } from 'apollo-cache-persist';
import { defaults, resolvers } from "./store";
const cache = new InMemoryCache();
persistCache({
storage: window.localStorage,
debug: process.env.NODE_ENV !== 'production',
cache
});
const client = new ApolloClient({
uri: process.env.NODE_ENV === 'production' ? 'https://five-yards-api.herokuapp.com/graphql' : 'http://localhost:7777/graphql',
credentials: 'include',
clientState: {
defaults,
resolvers
},
cache
});
// TODO: Doesn't work as expected
client.onResetStore(client.writeDefaults);
export default client;
I use Apollo Client 2.x, and Apollo Boost may be the same.
I had the same problem with Apollo Client 2.x, and below was the solution for me.
In the root file where you configure your Apollo (e.g. App.js):
cache.writeData({data : defaultData }); # I assume you already have this to initialise your default data.
# Then, you just add below underneath.
client.onResetStore(() => {
cache.writeData({data : defaultData });
});
const App = () => (...
AFAIK the only way to this is to migrate from Apollo Boost which configures a lot of things under the hood for you and set up Apollo Client manually. After migrating I was able to call onResetStore() as per the docs and everything is working :)
Apollo Boost migration:
https://www.apollographql.com/docs/react/advanced/boost-migration.html

Apollo Authentication with Gatsby

I am trying to implement the Authentication workflow officially described in the Apollo docs as I did before in other projects, but this time using Gatsby.
The idea seems pretty straightforward. Need to create/update gatsby-browser.js like when using redux but to initialise the ApolloClient and pass through ApolloProvider.
Something like:
import React from 'react'
import { Router } from 'react-router-dom'
import { ApolloProvider } from 'react-apollo'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { setContext } from 'apollo-link-context'
const httpLink = createHttpLink({
uri: process.env.ENDPOINT_URI,
credentials: 'same-origin',
})
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache().restore({}),
})
exports.replaceRouterComponent = ({ history }) => {
const ConnectedRouterWrapper = ({ children }) => (
<ApolloProvider client={client}>
<Router history={history}>{children}</Router>
</ApolloProvider>
)
return ConnectedRouterWrapper
}
Then login using
<Mutation
mutation={LOGIN_MUTATION}
refetchQueries={[{ query: CURRENT_USER_QUERY }]}
>
{/*...*/}
</Mutation>
and in another Component with something like the Status (where, if logged in shows x otherwise y):
<Query query={CURRENT_USER_QUERY} fetchPolicy="network-only">
{/*...*/}
</Query>
The problem
With this configuration, when the login receives data (so, credentials are ok), I save the token received in localStorage but the Status component do the Query with the previous (empty) token, so shows logged out, also doing something like history.push('/') after login.
Refreshing the page, since the item in localStorage was saved before, the Status shows as logged in.
Expected behaviour
The idea is to have the Status component updated after login avoiding doing a refresh of the page, for this reason I added refetchQueries={[{ query: CURRENT_USER_QUERY }]}.
I tried some things like pass props (no needed in case I change the route and use withRouter in the Status component), but seems to late, the Query was fired without the new token.
The main issue here is probably your Status component needs to be re-render only after the the token saved into localStorage. Right now it's probably re-render too early.
You can try wrapping your Status component with another wrapper component, and check for the token existence before rendering the Status component.
I believe your issue is related to how the links are defined. IMO, the AuthLink should be defined before the HttpLink (as middleware):
const client = new ApolloClient({
link: ApolloLink.from([
authLink,
httpLink
]),
cache: new InMemoryCache().restore({}),
})
While the documentation says that concat and from do the same thing, order is important. You want to have the context set before sending the request.

Resources