Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing multipart field 'operations' #164

Closed
adriaanbalt opened this issue Oct 14, 2019 · 16 comments
Closed

Missing multipart field 'operations' #164

adriaanbalt opened this issue Oct 14, 2019 · 16 comments
Labels

Comments

@adriaanbalt
Copy link

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:

Screen Shot 2019-10-14 at 5 37 07 PM

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()
@jaydenseric jaydenseric transferred this issue from jaydenseric/graphql-multipart-request-spec Oct 14, 2019
@jaydenseric
Copy link
Owner

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.

@mike-marcacci
Copy link
Collaborator

As @jaydenseric mentioned, you definitely can't use express-multipart-file-parser to consume and parse the incoming request stream, and then expect graphql-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.

@adriaanbalt
Copy link
Author

@mike-marcacci thanks for these details! To do #129 I will have to fork and link the NPM to test the implementation. I'm only sending images and eventually videos.

I was thinking to avoid using graphql to upload, go directly to my storage containers. What are the pros vs cons with these two options? Thanks!

@mike-marcacci
Copy link
Collaborator

@adriaanbalt if you are set on using cloud functions for your API, that may be a good workaround. They really aren't designed to do any sort of heavy lifting, and they have hard timeouts that will make uploads of large videos impossible.

IIRC Google Cloud Storage has pre-signed URLs, so it may be possible to:

  1. create a GraphQL mutation for generating and returning a pre-signed URL
  2. have the client upload the file to the pre-signed URL
  3. use Google Pub-Sub to listen for completion of an upload to that URL
  4. process the upload

From an API simplicity perspective this is not ideal, but from an implementation perspective it may do the trick.

I'm going to go ahead and close this issue, as I don't think it's a bug here :)

@adriaanbalt
Copy link
Author

adriaanbalt commented Oct 18, 2019

Thanks @mike-marcacci Just so you have a sense of what I'm trying to create:

I'm using cloud functions to "host" the GraphQL API like this

Originally I was going to setup my app like this:

  1. User creates a post of text and imagery (possibly video 1 day)
  2. When the user submits this new post, the app first uploads the image/video to Storage and separately creates a GraphQL mutation to update the DB with the path to the image in storage as well as the text.

Then I came across graphql-upload and thats when I became curious as to the potential to also send a file across the mutation. Unfortunately Cloud Functions http don't process multipart/form-data without busboy, which is under the hood of express-multipart-file-parser.

So it looks like I'm back to my original approach. Unless you can suggest an alternative? Thanks for all your help and guidance; loving and learning so much when using these utils!

@mike-marcacci
Copy link
Collaborator

@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).

@stevewirig
Copy link

stevewirig commented Jun 2, 2020

@adriaanbalt I have been headed down the exact same path as you with Firebase Functions. I have all of my file uploads working on local nodejs server, but when I run via the cloud functions I too am seeing the Bad Request Error. Before I keep digging and investigating my own potential solution and reverting back to interfacing directly with Firebase Storage, I wanted to get your thoughts on where you ended up landing with GraphQL FileUploads via firebase functions...

@Aslemammad
Copy link

For folks that encountered this issue, if you have middlewares like express-fileupload or fileParser, remove them; they won't let graphql-upload consume the data. That's how I solved it.

@ghost
Copy link

ghost commented Jul 25, 2021

I solve it, by adding uploads: false:
const server = new ApolloServer({ uploads:false, schema, playground: true });

@namadaza
Copy link

@Enmanuellperez98 +1 to this solution, was the fix for me as well. Not sure the root cause of the error, but I'll take it 😄

@ghost
Copy link

ghost commented Aug 2, 2021

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 with uploads: 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

new ApolloServer ({
   // Disable the built in file upload implementation that uses an outdated
   // `graphql-upload` version, see:
   // https://github.com/apollographql/apollo-server/issues/3508#issuecomment-662371289
   uploads: false,
   schema,
}). applyMiddleware ({app}); 

@Arideno
Copy link

Arideno commented Aug 9, 2021

Hi, a have this code in NestJS app module

GraphQLModule.forRoot({
      autoSchemaFile: true,
      sortSchema: true,
      uploads: false,
}),

but I also get "Missing multipart field 'operations'". Anybody knows how to figure this out?

@omar-dulaimi
Copy link

omar-dulaimi commented Oct 4, 2021

This can happen when using apollo server express to host a graphql and a rest api. The failing request was multipart. So to fix it, I changed it to application/x-www-form-urlencoded from the frontend.

Hope this helps someone.

@tsirolnik
Copy link

Had the same issue with "graphql-upload": "^12.0.0" and "apollo-server-express": "^2.2.3" adding uploads: false, solved it

@atomoc
Copy link

atomoc commented Dec 15, 2021

Hi, a have this code in NestJS app module

GraphQLModule.forRoot({
      autoSchemaFile: true,
      sortSchema: true,
      uploads: false,
}),

but I also get "Missing multipart field 'operations'". Anybody knows how to figure this out?

did you manage to solve the problem?

@tkssharma
Copy link

Guys any help here, i am getting the same error
{"correlationId":"6999c306-2480-4632-b8dd-954cf9e67bc0","level":"error","message":"[Fri May 27 12:44:48 2022] [error] Missing multipart field ‘operations’ (https://github.com/jaydenseric/graphql-multipart-request-spec)."}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests