Hope you could help me out with the following. I am trying to upload an excel file of ≈3MB from the client side to the API by first converting the file to a DataURL where after I send it as a string. This is working for smaller files, but it somehow seems to be blocking my larger files.
When I upload the file, I get the following error.
POST body missing. Did you forget use body-parser middleware?
I have done my own research and found more people with the same problem, though I could not find a solution.
https://github.com/apollographql/apollo-server/issues/792
This is the code I am using on the server side.
import { ApolloServer, gql } from 'apollo-server-micro'
type Props = {
_id: string
file: string[]
}
const typeDefs = gql`
type Mutation {
uploadFile(file: [String!]!): Boolean!
}
type Query {
readUpload(_id: String!): Boolean!
}
`
const resolvers = {
Mutation: {
async uploadFile(_: any, { file }: Props) {
console.log(file)
return true
}
},
Query: {
async readUpload(_: any, { _id }: Props) {
}
}
}
const apolloServer = new ApolloServer({
typeDefs,
resolvers
})
export const config = {
api: {
bodyParser: false
}
}
// Ensure to put a slash as the first character to prevent errors.
export default apolloServer.createHandler({ path: '/api/uploads' })
This is the code I am using on the client side.
import { useRef } from 'react'
import { uploadFile } from '../graphql/fetchers/uploads'
import { UPLOAD_FILE_QUERY } from '../graphql/queries/uploads'
export default function Upload() {
const inputElement = useRef<HTMLInputElement>(null)
const submitForm = (event: any) => {
event.preventDefault()
const files = inputElement.current?.files
if (files) {
const fileReader = new FileReader()
fileReader.onload = async () => {
try {
const result = fileReader.result as string
try {
console.log(result)
await uploadFile(UPLOAD_FILE_QUERY, { file: result })
} catch(error) {
console.log(error)
}
} catch(error) {
console.log(error)
}
}
fileReader.readAsDataURL(files[0])
}
}
return (
<form>
<input ref={inputElement} type='file'></input>
<button onClick={(event) => submitForm(event)}>Submit</button>
</form>
)
}
export const config = {
api: {
bodyParser: false
}
}
set bodyParser to true
Set bodyParser size limit
export const config = {
api: {
bodyParser: {
sizeLimit: '4mb' // Set desired value here
}
}
}
you try to send file as string in json? I think you should use multipart/form data on client side and parse them with special middleware on server side
On client special link converts request to multipart/formdata
full example https://github.com/jaydenseric/apollo-upload-examples
import { useMemo } from "react"
import { ApolloClient, createHttpLink, InMemoryCache } from "#apollo/client"
import { setContext } from "#apollo/client/link/context"
import { getUserTokenFromLocalStorage } from "../utils/utils"
import { createUploadLink } from "apollo-upload-client"
let apolloClient
const httpLink = createUploadLink({
uri: "/api/graphql",
headers: {
"keep-alive": "true",
},
})
const authLink = setContext((_, { headers }) => {
let token = getUserTokenFromLocalStorage()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
}
})
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("#apollo/client/link/schema")
const { schema } = require("./schema")
return new SchemaLink({ schema })
} else {
return authLink.concat(httpLink)
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState)
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
Related
I'm using Apollo Client for my nextJs project. I'm trying to create a storefront Shopify app but i probably not understand how apollo query works.
This is the error:
Server Error
Error: Variable $handle of type String! was provided invalid value.
[product].jsx
import { useQuery } from '#apollo/client'
import GET_PRODUCT_BY_HANDLE from '../../lib/graphql/queries/getProductByHandle'
// import GET_PRODUCT from '../../lib/graphql/queries/getProduct'
import { initApollo } from '../../lib/apollo'
// const VARIABLE = 'struccante-oleoso'
export default function Product({ queryByHandle }) {
const { data, loading, error } = useQuery(GET_PRODUCT_BY_HANDLE, {
variables: { handle: queryByHandle },
})
if (loading) return <h1>Loading...</h1>
if (error || !data) return <h2>Error</h2>
if (data.productByHandle.length === 0) return <h2>404 | Not Found</h2>
return (
<div>
<h1>Home</h1>
<h2>{data.productByHandle.handle}</h2>
</div>
)
}
export const getServerSideProps = async ({ query }) => {
const queryByHandle = query.productByHandle // if i replace with "struccante-oleoso" works well
const apolloClient = initApollo()
await apolloClient.query({
query: GET_PRODUCT_BY_HANDLE,
variables: { handle: queryByHandle },
})
return {
props: {
initialApolloState: apolloClient.cache.extract(),
queryByHandle,
},
}
}
getProductByHandle.jsx
import { gql } from '#apollo/client'
const GET_PRODUCT_BY_HANDLE = gql`
query getProductByHandle($handle: String!) {
productByHandle(handle: $handle) {
id
handle
description
images(first: 5) {
edges {
node {
url
altText
}
}
}
options {
name
values
id
}
variants(first: 25) {
edges {
node {
selectedOptions {
name
value
}
image {
url
altText
}
title
id
price {
amount
}
}
}
}
}
}
`
export default GET_PRODUCT_BY_HANDLE
apollo.jsx
import { ApolloClient, HttpLink, InMemoryCache } from '#apollo/client'
import { useMemo } from 'react'
const domain = process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
const storefrontAccessToken =
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN
let uri = `https://${domain}/api/2022-10/graphql.json`
let apolloClient
function createApolloClient() {
return new ApolloClient({
cache: new InMemoryCache({}),
ssrMode: typeof window === 'undefined',
link: new HttpLink({
uri: `${uri}`,
headers: {
'X-Shopify-Storefront-Access-Token': storefrontAccessToken,
'Content-Type': 'application/json',
Accept: 'application/json',
},
}),
})
}
export function initApollo(initialState = null) {
const client = apolloClient || createApolloClient()
if (initialState) {
client.cache.restore({
...client.extract(),
...initialState,
})
}
if (typeof window === 'undefined') {
return client
}
if (!apolloClient) {
apolloClient = client
}
return client
}
export function useApollo(initialState) {
return useMemo(() => initApollo(initialState), [initialState])
}
I expect to retrieve handle as a string from gql query
I've got a problem with sign up request on my local machine. When I set a project on my local machine from docker and tried to sign up to mt local server(localhost:3000), I got an error 401 Unauthorized, but I don't get this error when I git clone project fron github and run npm run dev. And it works fine when the project on my web server in internet.
Tokens should be saved in local storage, but there are no tokens at all.
I tried to set {withCredentials: true} but it didn't work too.
//storage.js
const storage = {
get: (key) => {return localStorage.getItem(key)}
}
export const getAccessToken = () => {
return storage.get('accessToken')
}
//authApi.js
import {$api_id} from '../api'
export const AuthApi = {
async signup(payload) {
const response = await $api_id.post('/auth/signup', payload)
return response
},
async signin(payload) {
const response = await $api_id.post('/auth/signin', payload)
return response
}
}
//api.js
import axios, {AxiosResponse} from 'axios'
import {getAccessToken, getDeviceId} from 'utils'
export const API_URL = process.env.NEXT_PUBLIC_API_KEY
$api_id.baseURL = process.env.NEXT_PUBLIC_API_KEY
export const $api_id = axios.create({
withCredentials: false,
baseURL: API_ID_URL,
})
$api_id.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${getAccessToken()}` //getAccessToken(storage.get('accessToken'))
if(config.data.avatar) {
const formData = new FormData()
formData.append('deviceId', getDeviceId()) //getDeviceId(storage.get("deviceId")) - fingerprintjs
Object.entries(config.data).forEach(([name, value]) => {
formData.append(name, value)
})
config.data = formData
} else {
let data
if(typeof config.data === 'string') {
data = JSON.parse(config.data)
} else {
data = config.data
}
if(!data.deviceId) {
data.deviceId = getDeviceId()
}
// console.log({data})
}
Thanks!
Since graphql-yoga V1 is no longer supported, I'd want to switch to graphql-yoga/node V2.
I've studied the official documentation on the website, but I'm having trouble migrating from V1 to V2.
Is a third-party package required?
here is a basic code:
const server = createServer({
schema: `type Query {
me: User!
posts(query: String): [Post!]!
users(query: String): [User!]!
comments(query: String): [Comment!]!
}`,
resolvers:{
Query: {
posts(parent, args, ctx, info) {
if (!args.query) {
return posts;
}
return posts.filter((post) => {
const isTitleMatch = post.title
.toLowerCase()
.includes(args.query.toLowerCase());
const isBodyMatch = post.body
.toLowerCase()
.includes(args.query.toLowerCase());
return isTitleMatch || isBodyMatch;
});
}
}
}
})
As you can see, I have resolvers and schema both are in single file named server.js
Could someone please assist me in this situation?
According to the docs it should be:
const server = createServer({
schema: {
typeDefs: `type Query {
me: User!
posts(query: String): [Post!]!
users(query: String): [User!]!
comments(query: String): [Comment!]!
}`,
resolvers: {
Query: {
posts(parent, args, ctx, info) {
if (!args.query) {
return posts;
}
return posts.filter((post) => {
const isTitleMatch = post.title
.toLowerCase()
.includes(args.query.toLowerCase());
const isBodyMatch = post.body
.toLowerCase()
.includes(args.query.toLowerCase());
return isTitleMatch || isBodyMatch;
});
}
}
}
}
})
anyway, here is an example for basic setup with typedefs and resolver in external files. note that it uses graphql-tools for uploading .graphql file for the schema, but you can easily use same method as resolvers for schema as .js file:
import { createServer } from '#graphql-yoga/node';
import { resolvers } from './resolvers.js';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { loadFiles } from '#graphql-tools/load-files';
const getSchema = async () =>
makeExecutableSchema({
typeDefs: await loadFiles('./*.graphql'),
resolvers,
});
async function main() {
const schema = await getSchema();
const server = createServer({ schema });
await server.start();
}
main();
I am making a simple next js blog type of application using graphql as a data fetching backend to render text. Using getStaticPaths, I'm running into the following error when I try to fetch data for my page.
ReferenceError: Cannot access 'getAllPostIds' before initialization
Here is my code:
pages/posts/[id].tsx
import { getAllPostIds } from '../../../lib/posts'
const Post = ({ postData }) => {
... code.....
}
export const getStaticPaths = async () => {
const paths = getAllPostIds('aws');
return {
paths,
fallback: false
}
}
export default Post;
And here is my posts.ts where I use graphql to fetch data.
import { useQuery } from "react-query";
import { GraphQLClient } from "graphql-request";
const GET_POST_IDS = gql`
query($folder: String!) {
repository(owner: "assembleinc", name: "documentation") {
object(expression: $folder) {
... on Tree {
entries {
name
}
}
}
}
}`
;
const graphQLClient = new GraphQLClient('https://api.github.com/graphql', {
headers: {
Authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`
}
});
export const getAllPostIds = (folder: String) => {
return useQuery(folder, async () => {
... fetch data ...
});
}
Essentially, before I can even get the data through graphql, next js is complaining that getAllPostIds can't be initialized even though I import it at the top. Is there some next.js magic that I am not seeing?
I'm coming from React/Redux-land and am slowly getting acquainted to Svelte design patterns using stores.
Currently I'm curious to figure out if this is an acceptable pattern or if not, what is a better way to pursue this kind of communication. The basic premise is I want to be able to update multiple custom stores (which are using writable) from an adjacent store.
In the example below I have "loading.js" and "error.js" stores which would be used globally, commented out in the "session.js" store. I'd like to update these based on the result of an API request to create a session, in order to keep most of my heavy lifting out side of components.
My current thinking is that I'd pass each store needed through the "createSessionStore" function, but it feels a little clunky as it would highly depend on the declaration order of each store within "store.js"
The long term intention for wishing to do it this way is so I can add any kind of communication layer (such as web sockets) in to the mix and update the global loading or error store from any layer.
Thanks for the help.
Component.svelte
<script>
import { onMount } from "svelte";
import { error, loading, session } from "./store";
onMount(() => {
session.fetchSession();
});
</script>
{#if $loading}
<div>Loading...</div>
{/if}
{#if $error}
<div>Something went wrong: {$error}</div>
{/if}
store.js
import { createErrorStore } from "./error";
import { createLoadingStore } from "./loading";
import { createSessionStore } from "./session";
export const error = createErrorStore();
export const loading = createLoadingStore();
export const session = createSessionStore();
session.js
import { writable } from "svelte/store";
const INITIAL_STORE = {
token: null
};
export const createSessionStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const fetchSession = async () => {
// loading.set(true);
try {
const response = await fetch("MY_API_ENDPOINT/auth/token", {
method: "POST",
});
if (!response.ok) {
const err = new Error("Network response was not ok.");
// error.set(err);
// loading.set(false);
return;
}
const data = await response.json();
set(data.token);
// loading.set(false);
} catch (err) {
// error.set(err);
// loading.set(false);
}
};
const reset = () => {
set(initialStore);
};
return {
subscribe,
fetchSession,
reset
};
};
error.js
import { writable } from "svelte/store";
const INITIAL_STORE = false;
export const createErrorStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const reset = () => {
set(initialStore);
};
return {
subscribe,
set,
reset
};
};
loading.js
import { writable } from "svelte/store";
const INITIAL_STORE = false;
export const createLoadingStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const reset = () => {
set(initialStore);
};
return {
subscribe,
set,
reset
};
};
Interesting idea.
The problem here is that during the creation of the stores, not all of them exists yet. The only solution that I see for this is to add the references after creating them.
Here's my idea:
In the session.js:
import { writable } from "svelte/store";
const INITIAL_STORE = {
token: null
};
export const createSessionStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const fetchSession = async () => {
// loading.set(true);
try {
otherStores.loading && otherStores.loading.set(true);
const response = await fetch("MY_API_ENDPOINT/auth/token", {
method: "POST",
});
if (!response.ok) {
const err = new Error("Network response was not ok.");
otherStores.error && otherStores.error.set(err);
otherStores.loading && otherStores.loading.set(false);
return;
}
const data = await response.json();
set(data.token);
} catch (err) {
otherStores.error && otherStores.error.set(err);
otherStores.loading && otherStores.loading.set(false);
}
};
const reset = () => {
set(initialStore);
};
let otherStores = {}
const setOtherStores = (stores) => {
otherStores=stores
};
return {
subscribe,
fetchSession,
reset,
setOtherStores
};
};
In the store.js:
import { createErrorStore } from "./error";
import { createLoadingStore } from "./loading";
import { createSessionStore } from "./session";
export const error = createErrorStore();
export const loading = createLoadingStore();
export const session = createSessionStore();
session.setOtherStores({error,loading})
You can use the same pattern for any of the other stores (if needed), and after creation pass them the references to the other stores.