graphql-upload: Missing multipart field 'operations'
Firstly, 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()
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 16 (1 by maintainers)
I solve it, by adding
uploads: false
:const server = new ApolloServer({ uploads:false, schema, playground: true });
@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).
As @jaydenseric mentioned, you definitely can’t use
express-multipart-file-parser
to consume and parse the incoming request stream, and then expectgraphql-upload
to do the same, as it’s already been consumed.I think it’s safe to say that you can close your other issue at least as it relates to its use in conjunction with this library.
I also see that you’re running this in Google Cloud Functions. The HTTP emulator for cloud functions deviates from node’s standards in some important ways. You can make this work, but it’s not optimized for that use case. If you know that you’ll just be dealing with small files, this isn’t a serious concern. However, if you expect large files or serious traffic, you may want to start an API-compatible version of this package that plays more nicely with Google Could Functions.
I may be missing something about your use case, so I’ll leave this open for now, but I suspect that this can be closed in favor of #129.
graphql-upload
is implemented under the hood by Apollo Server (if you have an up to date version), so you don’t need to set it up manually.You definitely don’t want the
app.use(fileParser)
bit, it could interfere.I don’t know much about Firebase, but if you check the issues here you will see that some cloud environments (particularly serverless ones) don’t support standard multipart requests, or they do, but they do all the parsing for you and pass the result on from memory.
Hi, a have this code in NestJS app module
but I also get “Missing multipart field ‘operations’”. Anybody knows how to figure this out?
Hey @namadaza, It seems that in previous versions of Apollo Server, they had a built-in integration with an old version of
graphql-upload
. So you have to indicate withuploads: false
, that you are integrating it yourself.Take a look at this implementation of the repo author: https://github.com/jaydenseric/apollo-upload-examples/blob/master/api/server.mjs
For folks that encountered this issue, if you have middlewares like
express-fileupload
orfileParser
, remove them; they won’t letgraphql-upload
consume the data. That’s how I solved it.