Missing multipart field 'operations'
See original GitHub issueFirstly, thanks for all this great work; I appreciate it.
I’ve been trying to send files with GraphQL over Firebase HTTP Cloud Functions and upload them to Firebase Storage as well as update Firebase Firestore DB; ideally using a Firebase Transaction. For some reason I keep getting the following error:
BadRequestError: Missing multipart field ‘operations’ (https://github.com/jaydenseric/graphql-multipart-request-spec).
I’ve tried a bunch of things using Busboy, Rawbody and Express-Multipart-File-Parser; you can see a conversation I’ve been having with snippets of my code in this “issue” here. Note: code snippets are also copied at the bottom
Even with the above setup (using uploads
property when using ApolloServer, etc) I am still getting the BadRequestError
. My headers look like this:
And here is a curl:
curl 'http://localhost:5000/fairplay-app/us-central1/api'
-H 'accept: */*' -H 'Referer: http://localhost:3000/add'
-H 'Origin: http://localhost:3000'
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
-H 'Sec-Fetch-Mode: cors'
-H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQPiCZ99VZAAqVJQY' --data-binary $'------WebKitFormBoundaryQPiCZ99VZAAqVJQY\r\nContent-Disposition: form-data; name="operations"\r\n\r\n{"operationName":"SingleUpload","variables":{"file":null},"query":"mutation SingleUpload($file: Upload\u0021) {\\n singleUpload(file: $file) {\\n filename\\n mimetype\\n encoding\\n __typename\\n }\\n}\\n"}\r\n------WebKitFormBoundaryQPiCZ99VZAAqVJQY\r\nContent-Disposition: form-data; name="map"\r\n\r\n{"1":["variables.file"]}\r\n------WebKitFormBoundaryQPiCZ99VZAAqVJQY\r\nContent-Disposition: form-data; name="1"; filename="cooktop-scratches.jpg"\r\nContent-Type: image/jpeg\r\n\r\n\r\n------WebKitFormBoundaryQPiCZ99VZAAqVJQY--\r\n' --compressed
It appears that I am sending operations
in Form Data. What am I missing with my server setup?
Server:
const express = require('express')
const cors = require('cors');
const { ApolloServer } = require('apollo-server-express')
const fileParser = require('express-multipart-file-parser')
const schema = require('./schema')
const resolvers = require('./resolvers')
const app = express();
// cors allows our server to accept requests from different origins
app.use(cors());
app.options('*', cors());
app.use(fileParser) // supposedly this will fix the issue but doesn't seem to work
// setup server
const server = new ApolloServer({
typeDefs: schema,
resolvers,
introspection: true, // so we can access the playground in production reference: https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructor-options-lt-ApolloServer-gt
playground: true,
uploads: {
// Limits here should be stricter than config for surrounding
// infrastructure such as Nginx so errors can be handled elegantly by
// graphql-upload:
// https://github.com/jaydenseric/graphql-upload#type-processrequestoptions
maxFileSize: 10000000, // 10 MB
maxFiles: 1
},
})
server.applyMiddleware({ app, path: '/', cors: true })
React Front End:
import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloProvider } from 'react-apollo'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { createUploadLink } from 'apollo-upload-client'
import { Provider } from 'react-redux'
import store, { history } from './Store'
import { ConnectedRouter } from 'react-router-redux'
import App from './App'
import registerServiceWorker from './lib/serviceWorker'
import './index.scss'
import { GRAPHQL_URL} from './constants/graphql'
const uploadLink = createUploadLink({
uri: GRAPHQL_URL, // Apollo Server is served from port 4000
headers: {
"keep-alive": "true"
}
})
const apolloCache = new InMemoryCache()
const client = new ApolloClient({
cache: apolloCache,
link: uploadLink,
uri: GRAPHQL_URL,
});
ReactDOM.render(
<ApolloProvider client={client}>
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
</ApolloProvider>,
document.getElementById('root')
)
registerServiceWorker()
Issue Analytics
- State:
- Created 4 years ago
- Comments:16 (1 by maintainers)
@adriaanbalt, yes graphql-upload would be a very elegant solution, but isn’t a great fit for cloud functions. (Large file uploads in general aren’t a great fit for cloud functions…)
I think what you are describing is a perfectly fine strategy, especially if you’re in control of both the server and client. I will mention, though, that you will want to be able to control who has access to upload files to prevent potentially costly abuse of your systems (hence my reference to pre-signed URLs).
I solve it, by adding
uploads: false
:const server = new ApolloServer({ uploads:false, schema, playground: true });