Skip to content
This repository has been archived by the owner on Mar 20, 2023. It is now read-only.

Example with multer #9

Closed
gyzerok opened this issue Sep 4, 2015 · 12 comments
Closed

Example with multer #9

gyzerok opened this issue Sep 4, 2015 · 12 comments

Comments

@gyzerok
Copy link

gyzerok commented Sep 4, 2015

Can you provide an example how to use this middleware with multer?

I do not understand how to correctly handle file uploads.

@philostler
Copy link

+1

1 similar comment
@xpepermint
Copy link

+1

@mmurray
Copy link

mmurray commented Sep 25, 2015

I just took a shot at wiring this up, so I'll share what I came up with and maybe @leebyron can comment on how to approach this in a more idiomatic way (I'm very new to GraphQL)

First I created a GraphQLFile type to represent the files in my mutations. This type just has the shape of the file object produced by multer:

GraphQLFile = new GraphQLInputObjectType({
    name: 'File',
    description: 'A file uploaded via multipart/form-data',
    fields: () => ({
      fieldname: {
        type: new GraphQLNonNull(GraphQLString),
        description: 'The fieldname used to POST this file.',
      },
      originalname: {
        type: new GraphQLNonNull(GraphQLString),
        description: 'The original file name.',
      },
      buffer: {
        type: new GraphQLNonNull(GraphQLBuffer),
        description: 'The file buffered in memory',
      },
...

Then I setup a multer middleware that accepts any file fieldname referenced in my schema as a GraphQLFile input. So my configuration of multer looks something like this (where validFileFieldnames is derived by inspecting my schema):

multer({
  storage: multer.memoryStorage()
}).fields(validFileFieldnames.map(name => ({name, maxCount: 1})));

The next missing piece was moving the file objects from where multer puts them to where express-graphql expects them to be. To do that, I wrapped the multer middleware in my own middleware that parses req.body.variables and moves the files onto req.body.variables.input, which looks something like this:

(req, res, next) => {
  multerMiddleware(req, res, () => {
    if (!req.files || req.files.length === 0) {
      next();
      return;
    }
    // Parse variables so we can add to them. (express-graphql won't parse them again once populated)
    req.body.variables = JSON.parse(req.body.variables);
    req.files.forEach(file => {
      req.body.variables.input[file.fieldname] = file;
    });
    next();
  });
};

On the clientside I generate forms with validation wired up based on my schema, so for the GraphQLFile inputs I just render a file upload field. In my Relay mutations the files are included in the getFiles method as shown here: https://facebook.github.io/relay/docs/api-reference-relay-mutation.html#getfiles-example and after being sent to the server and buffered by multer I am able to access the files in mutateAndGetPayload just like any other field:

const GraphQLAddWidgetMutation = mutationWithClientMutationId({
  name: 'AddWidget',
  inputFields: {
    name: {type: new GraphQLNonNull(GraphQLString)},
    widgetPhoto: {type: new GraphQLNonNull(GraphQLFile)},
  },
  mutateAndGetPayload: ({name, widgetPhoto}) => {
    console.log(widgetPhoto); // { originalname: 'MyPhoto.jpeg', buffer: <Buffer ...

@leebyron
Copy link
Contributor

There is an example of this in express-graphql's tests:

https://github.com/graphql/express-graphql/blob/master/src/__tests__/http-test.js#L461

Generally I suggest including a reference to the request body in the "rootValue" provided to graphql such that any field resolutions which need to access the uploaded files.

I think @murz's approach is also quite clever.

@leebyron
Copy link
Contributor

I expanded on the example in the test along with more comments and link to it from the README

@nickretallack
Copy link

How would you express this mutation using the mutationWithClientMutationId from graphql-relay-js?

Also, can this be modified to present the file as a stream instead of fully draining it to disk or memory first?

@nickretallack
Copy link

Hm. I added multer but I'm still getting the error "Must provide query string." from graphql-express when I try to do a multipart request...

@nickretallack
Copy link

Oh, my bad. I have to make sure that middleware comes first.

@johnconley
Copy link

@nickretallack Would you mind explaining how you got the mutation to work using mutationWithClientMutationId?

@jkettmann
Copy link

If anyone has problems with this topic: I made an example repo basically for authentication with Relay and GraphQL but it also contains an image upload based on murz's answer.

@hassaantariq50
Copy link

can someone please guide me about the types, controllers and mutation for uploading a picture though GraphQL

@danielrearden
Copy link
Collaborator

danielrearden commented Jun 23, 2020

@hassaaneforte There's several examples above. You may also find it helpful to just use graphql-upload. It works with express-graphql and provides an Upload scalar for you. See the docs for more details.

If you have additional questions, please post them on Stack Overflow or utilize any of the other available community resources, like the official Slack channel.

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

No branches or pull requests

10 participants