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

File upload #37

Open
MichalLytek opened this issue Mar 2, 2018 · 54 comments
Open

File upload #37

MichalLytek opened this issue Mar 2, 2018 · 54 comments
Labels
Enhancement 🆕 New feature or request

Comments

@MichalLytek
Copy link
Owner

Integration with https://github.com/jaydenseric/apollo-upload-server

@MichalLytek MichalLytek added the Enhancement 🆕 New feature or request label Mar 2, 2018
@MichalLytek MichalLytek added this to the Future release milestone Mar 2, 2018
@daviddlv
Copy link

Hi,

Any news about this feature ?

I tried using https://github.com/jaydenseric/apollo-upload-server, i configured a upload resolver like this :

import {GraphQLUpload} from "apollo-upload-server";
...
@Mutation(returns => Media)
async upload(@Arg('file') file: GraphQLUpload): Promise<Media> {
    // process upload
    const media = this.repository.create({});
    return await this.repository.save(media);
}

But i've got this error :
UnhandledPromiseRejectionWarning: Error: You need to provide explicit type for UploadResolver#upload parameter #0

How to fix this error ?
Thanks

@MichalLytek
Copy link
Owner Author

MichalLytek commented Jul 25, 2018

https://19majkel94.github.io/type-graphql/docs/scalars.html#custom-scalars

You need to provide the scalar type to the @Arg decorator due to limited TypeScript reflection capability. And the TS type is Upload object, not the GQL scalar instance.

async upload(@Arg('file', type => GraphQLUpload) file: Upload): Promise<Media> {

@daviddlv
Copy link

Thanks @19majkel94 it works

@danpaugo
Copy link

danpaugo commented Aug 4, 2018

@19majkel94 where do you import TS type Upload from?

@MichalLytek
Copy link
Owner Author

MichalLytek commented Aug 5, 2018

@danpaugo
I've found some types definition in github issue:
jaydenseric/graphql-upload#70
And I've enhanced them with Upload interface:

declare module "apollo-upload-server" {
  import { GraphQLScalarType } from "graphql";
  import { RequestHandler } from "express";
  import { Readable } from "stream";

  export interface UploadMiddlewareOptions {
    maxFieldSize?: number;
    maxFileSize?: number;
    maxFiles?: number;
  }

  export interface Upload {
    stream: Readable;
    filename: string;
    mimetype: string;
    encoding: string;
  }

  export const GraphQLUpload: GraphQLScalarType;
  export function apolloUploadExpress(
    options?: UploadMiddlewareOptions,
  ): RequestHandler;
}

I will try to upload this definition to DefinitelyTyped repo to make @types work.

@zjjt
Copy link

zjjt commented Jan 14, 2019

Hi , i still experience troubles with it the package now renamed graphql-upload should still be working but i keep having

the cannot find declaration types

issue despite the fact that i used the example shown above...it is like tsnode completely ignores the declaration files.

@zjjt
Copy link

zjjt commented Jan 14, 2019

Here is the error i keep having. I use apollo-server 2.3.1 which comes embedded with graphql-upload 5new name for apollo-server-upload).
Here is what i am getting as errors:

node:60159) UnhandledPromiseRejectionWarning: TSError: ⨯ Unable to compile TypeScript:
src/GraphqlExpress/resolvers/FileUpload_resolver.ts(3,29): error TS7016: Could not find a declaration file for module 'graphql-upload'. '/Users/macbook/Desktop/Z-projects/GESSMS/node_modules/graphql-upload/lib/index.js' implicitly has an 'any' type.
Try npm install @types/graphql-upload if it exists or add a new declaration (.d.ts) file containing declare module 'graphql-upload';

at createTSError (/Users/macbook/Desktop/Z-projects/GESSMS/node_modules/ts-node/src/index.ts:261:12)
at getOutput (/Users/macbook/Desktop/Z-projects/GESSMS/node_modules/ts-node/src/index.ts:367:40)
at Object.compile (/Users/macbook/Desktop/Z-projects/GESSMS/node_modules/ts-node/src/index.ts:558:11)
at Module.m._compile (/Users/macbook/Desktop/Z-projects/GESSMS/node_modules/ts-node/src/index.ts:439:43)
at Module._extensions..js (module.js:663:10)
at Object.require.extensions.(anonymous function) [as .ts] (/Users/macbook/Desktop/Z-projects/GESSMS/node_modules/ts-node/src/index.ts:442:12)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Module.require (module.js:596:17)

(node:60159) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 4)
(node:60159) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate theNode.js process with a non-zero exit code.

Can you help me please?

@MichalLytek
Copy link
Owner Author

MichalLytek commented Jan 14, 2019

@zjjt

it is like tsnode completely ignores the declaration files.

Can you help me please?

It's not a problem with TypeGraphQL itself.
Please read the docs for ts-node or open an issue in ts-node repository.

@zjjt
Copy link

zjjt commented Jan 14, 2019

@19majkel94 thank you for replying.

@johinsDev
Copy link

@daviddlv have you a example? for upload with scalar on type-graphql?

@daviddlv
Copy link

@johinsDev , here an example of my resolver :
https://gist.github.com/daviddlv/0259ce8b64a3e23611a96b0c52c27a27

@johinsDev
Copy link

johinsDev commented Jan 30, 2019

@johinsDev , here an example of my resolver :
https://gist.github.com/daviddlv/0259ce8b64a3e23611a96b0c52c27a27

thanks, its cool.
but what have you FileInput?

@daviddlv
Copy link

I put the FileInput file in the gist

@laukaichung
Copy link

laukaichung commented Apr 18, 2019

@daviddlv @19majkel94

I tried to use your gist, but I'm getting (node:18235) UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL input type for stream.

import { Stream } from 'stream';
import { Field, InputType, Int } from 'type-graphql';

@InputType()
export class FileInput {
  @Field(type => Stream) // <<== the problem
  stream: Stream;

  @Field() filename: string;

  @Field() mimetype: string;

  @Field() encoding: string;
}

Here's the js output:

const type_graphql_1 = require("type-graphql");
const stream_1 = require("stream");
let FileInput = class FileInput {
};
__decorate([
    type_graphql_1.Field(type => stream_1.Stream),
    __metadata("design:type", stream_1.Stream)
], FileInput.prototype, "stream", void 0);
__decorate([
    type_graphql_1.Field(),
    __metadata("design:type", String)
], FileInput.prototype, "filename", void 0);
__decorate([
    type_graphql_1.Field(),
    __metadata("design:type", String)
], FileInput.prototype, "mimetype", void 0);
__decorate([
    type_graphql_1.Field(),
    __metadata("design:type", String)
], FileInput.prototype, "encoding", void 0);
FileInput = __decorate([
    type_graphql_1.InputType()
], FileInput);
exports.FileInput = FileInput;

@MichalLytek
Copy link
Owner Author

@laukaichung
All you need is:

@Arg('file', type => GraphQLUpload)
file: FileInput

the FileInput might be just an TS interface, no need for an @InputType because it won't work.

@sgtkuncoro
Copy link

sgtkuncoro commented May 31, 2019

hi @19majkel94 , i have issue,, with arg type of array,
here is example:

@ArgsType()
export class XArg {
  @Field()
  name: string;

  @Field({ nullable: true })
  value?: string;
}
// resolver

@Mutation(returns => Response)
  async xCreate(
    @Ctx() context: Context,
    @Arg("xarr") xarr: Array<XArg>
  ): Promise<ResponseInterface> {}

Error:
You need to provide explicit type for XResolver#xCreate parameter #1

thanks before

@MichalLytek
Copy link
Owner Author

@sgtkuncoro

provide explicit type

@Arg("xarr", type => [XArg]) xarr

@andfk
Copy link

andfk commented Sep 11, 2019

Working solution for me:

import { Resolver, Mutation, Arg } from 'type-graphql'
import { GraphQLUpload, FileUpload } from 'graphql-upload'
import os from 'os'
import { createWriteStream } from 'fs'
import path from 'path'

@Resolver()
export default class SharedResolver {
  @Mutation(() => ImageResource)
  async uploadImage(
    @Arg('file', () => GraphQLUpload)
    file: FileUpload
  ): Promise<ImageResource> {
    const { createReadStream, filename } = await file

    const destinationPath = path.join(os.tmpdir(), filename)

    const url = await new Promise((res, rej) =>
      createReadStream()
        .pipe(createWriteStream(destinationPath))
        .on('error', rej)
        .on('finish', () => {
          // Do your custom business logic

          // Delete the tmp file uploaded
          unlink(destinationPath, () => {
            res('your image url..')
          })
        })
    )

    return {
      url
    }
  }
}

@jordan-jarolim
Copy link

jordan-jarolim commented Oct 4, 2019

Thank you for proposed solution @andfk @daviddlv . It seems to be working until I try to upload more than 10 files at once. In that case I am getting this warning:

MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 exit listeners added. Use emitter.setMaxListeners() to increase limit
demo:  |     at _addListener (events.js:280:19)
demo:  |     at process.addListener (events.js:297:10)
demo:  |     at _fs.default.open (***/node_modules/fs-packages/api/capacitor/lib/index.js:140:17)
demo:  |     at FSReqWrap.oncomplete (fs.js:135:15)

My resolver looks like this:

const fs = require('fs');
import { UploadResult } from '../graphql/models/file';
import { GraphQLUpload, FileUpload } from 'graphql-upload';
import { Resolver, Mutation, Arg } from 'type-graphql';
import isArray from 'lodash/isArray';

@Resolver()
class FileResolver {
  @Mutation(() => UploadResult)
  async fileUpload(@Arg('fileInput', () => GraphQLUpload) fileInput: FileUpload): Promise<UploadResult> {
    let readableStreams = [];
    if (isArray(fileInput)) {
      readableStreams = await Promise.all(fileInput);
    } else {
      readableStreams[0] = await fileInput;
    }
    const pipedStreams = readableStreams.map((readStreamInstance) => {
      const { filename, createReadStream } = readStreamInstance;
      const writableStream = fs.createWriteStream(`./${filename}`, { autoClose: true });
      return new Promise<string>((resolve, reject) => {
        createReadStream()
          .pipe(writableStream)
          .on('error', (error: any) => {
            reject(error);
          })
          .on('finish', () => {
            resolve(filename);
          });
      })
    });
    const results = await Promise.all(pipedStreams);
    return {
      uploaded: results
    }
  }
}

The warning gets fired even if I comment-out the whole resolver-function body and just return some random string array. So I suspect that the error is not coming from the resolver itself. It's rather coming from some underlying implementation.

Does anybody has similar problem?

@jordan-jarolim
Copy link

So the warning is coming from eventemitter which is used by nodejs streams. You can bypass the warning by require('events').EventEmitter.defaultMaxListeners = uploadMultipleMaxFiles; I wanted to be sure that I don't have any memory leakage, so I wrote a "test" with multiple uploads - https://gist.github.com/jordan-jarolim/4d374c1b864de4c6321f611f748dc5bd and check memory management using --expose-gc --inspect=9222 flags for node process and chrome://inspect tool.

@alondahari
Copy link

Would love to use this, but when I tried I got this error message:
Error: This implementation of ApolloServer does not support file uploads because the environment cannot accept multi-part forms

This happens when I try to set the uploads key on the new ApolloServer I'm creating. Without it, I get a invalid json error. Any ideas?

@MOTORIST
Copy link

Apollo Server version? Apolo + express, + koa ...?

@naishe
Copy link

naishe commented Feb 28, 2020

Hey guys, @andfk implementation does not work for me. It fails with this message: got invalid value {}; Expected type Upload. Upload value invalid.

This is what my resolver looks like:

import { Resolver, Mutation, Arg, Int } from 'type-graphql';
import { GraphQLUpload, FileUpload } from 'graphql-upload';

@Resolver()
export class UploadResolver {
  @Mutation(_ => Int)
  singleUpload(@Arg('file', () => GraphQLUpload) file: FileUpload) {
    console.log(file);
    return 3;
  }
}

This is the query I execute (I have also tried in the app with same result):

curl localhost:3333/graphql \
  -F operations='{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) }", "variables": { "file": null } }' \
  -F map='{ "0": ["variables.file"] }' \
  -F [email protected]
{
"errors": [
{
"message":"Variable \"$file\" got invalid value {}; Expected type Upload. Upload value invalid.",
"locations":[{"line":1,"column":11}],
"extensions":{"code":"INTERNAL_SERVER_ERROR",
"exception": {
  "message":"Upload value invalid.",
  "stacktrace":[
    "GraphQLError: Upload value invalid.",
    "    at GraphQLScalarType.parseValue (/project/node_modules/graphql-upload/lib/GraphQLUpload.js:66:11)",
    "    at coerceInputValueImpl 
[-- snip --]

The relevant dependencies as follows:

    "apollo-server-express": "^2.10.0",
    "express": "4.17.1",
    "graphql": "^14.6.0",
    "graphql-tag": "^2.10.3",
    "graphql-upload": "^10.0.0",

Am I missing something?

FWIW, here is the React implementation of upload, that returns the same error:

import React from 'react';
import { useApolloClient, useMutation } from '@apollo/react-hooks';
import gql from 'graphql-tag';

const SINGLE_UPLOAD_MUTATION = gql`
  mutation singleUpload($file: Upload!) {
    singleUpload(file: $file)
  }
`;

export const UploadFile = () => {
  const [uploadFileMutation] = useMutation(SINGLE_UPLOAD_MUTATION);
  const apolloClient = useApolloClient();

  const onChange = ({
    target: {
      validity,
      files: [file]
    }
  }) =>
    validity.valid &&
    uploadFileMutation({ variables: { file } }).then(() => {
      apolloClient.resetStore();
    });

  return <input type="file" required onChange={onChange} />;
};

@MichalLytek
Copy link
Owner Author

@naishe have you added the express middleware?

@naishe
Copy link

naishe commented Feb 28, 2020

Yes, I like this:

const app = express();
// -- snip --
apolloServer.applyMiddleware({ app });

I think I figured the problem. import { GraphQLUpload, FileUpload } from 'graphql-upload'; was the issue. It seems the graphql-upload library is not needed with the latest Apollo Server. The following code works:

import { Resolver, Mutation, Arg, Int } from 'type-graphql';
import { GraphQLUpload } from 'apollo-server-express';

export interface FileUpload {
  filename: string;
  mimetype: string;
  encoding: string;
  createReadStream(): ReadStream;
}

@Resolver()
export class UploadResolver {
  @Mutation(_ => Int)
  async singleUpload(@Arg('file', () => GraphQLUpload) file: FileUpload) {
    console.log(file);
    return 4; // ideally, you'd return something sensible like an URL 
}

@theerfan
Copy link

theerfan commented Mar 14, 2020

@hemedani
But how?
When I copy your code I get this on the Promise<Boolean> line:

Value of type 'PromiseConstructor' is not callable. Did you mean to include 'new'?

@karladler
Copy link

karladler commented Apr 1, 2020

I finally think I have tested all possible here posted solutions, but end up all the time with this error. Has anybody the same problem or is it just me?

(node:20149) UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for stream
at Function.getGraphQLOutputType (..../node_modules/type-graphql/dist/schema/schema-generator.js:395:19)

Edit: NVM, it came from another Module, where I used Buffer as type 🤦‍♂️

@theerfan just use @Mutation( () => Boolean ) without Promise constructor

@aramisromero89
Copy link

aramisromero89 commented Jul 28, 2020

Please help I am getting "Variable "$file" got invalid value {}; Upload value invalid." when calling mutation from apollo client

import { Mutation, Resolver, Args, Query, FieldResolver, Root, Arg } from 'type-graphql';
import { Person, FindManyWorkerArgs, Position } from 'prisma/generated/type-graphql';
import { PersonService } from '../services/person.service';
import { CreateWorkerArgs, UpdateWorkerArgs } from '../dtos/person.input';
import { Worker, File } from 'prisma/generated/type-graphql'
import { GraphQLUpload, FileUpload } from 'graphql-upload';

import { PaginatedWorker } from '../dtos/paginated-person.type';
import { WorkerService } from '../services/worker.service';
import { PositionService } from 'src/position/services/position.service';
import { Inject } from 'typedi';
import { BadRequestException } from '@nestjs/common';


@Resolver(of => Worker)
export class WorkerResolver {

    constructor(
        private readonly workerService: WorkerService,    
        private readonly personService: PersonService,
        private readonly positionService: PositionService
    ) { }
    
    @Mutation(returns => Worker)
    async createWorker(@Args() args: CreateWorkerArgs, @Arg('file', type => GraphQLUpload, { nullable: true }) file?: FileUpload): Promise<Worker> {
        return this.workerService.createWorker(args.data ,  file);
    }
}

@aramisromero89
Copy link

Solution found:
Importing GraphQLUpload from apollo-server and FileUpload from graphql-upload solved the problem

@JeremyBernier
Copy link

JeremyBernier commented Aug 16, 2020

Not working for me on Node.js v14.6.0. Have basically tried every combination, but keep getting the following error:

[GraphQL Error]: {
  "message": "Missing multipart field ‘operations’ (https://github.com/jaydenseric/graphql-multipart-request-spec).",
  "extensions": {
    "code": "INTERNAL_SERVER_ERROR"
  }
}

Running the following cURL command (notice how "operations" is clearly specified):

curl --location --request POST 'http://localhost:4000/graphql' \
--header 'Accept-Encoding: gzip, deflate, br' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Connection: keep-alive' \
--header 'DNT: 1' \
--header 'Origin: http://localhost:4000' \
--form 'operations={"query":"mutation($file: Upload!) {\n  uploadRandomFile(file:$file)\n}"}' \
--form 'map={"0": ["variables.file"]}' \
--form '0=@/C:/Users/somepic.png'

Resolver

import { GraphQLUpload, FileUpload } from "graphql-upload";
import { createWriteStream } from "fs";

@Mutation(returns => Boolean)
  async uploadRandomFile(@Arg("file", () => GraphQLUpload)
  {
    createReadStream,
    filename,
  }: FileUpload) {
    return new Promise(async (resolve, reject) =>
      createReadStream()
        .pipe(createWriteStream(`${__dirname}/../../../uploads/${filename}`, { autoClose: true }))
        .on("finish", () => resolve(true))
        .on("error", () => reject(false))
    );
  }

Any ideas?

EDIT: I'm an idiot, turns out I had accidentally put the uploads: false inside context in the ApolloServer constructor. All working now

@sgentile
Copy link

sgentile commented Sep 9, 2020

Great examples here. My question is around a multi-part form submission. Do you have multiple Args in this case, or create an input with a property of GraphQLUpload ?

@aseerkt
Copy link

aseerkt commented Dec 9, 2020

Hello guys, I am having trouble to get the file upload working. Only a fraction of file gets uploaded. And my mutation resolver returning null instead of boolean.

// PostResolver.ts
@Mutation(() => Boolean)
  @UseMiddleware(isAuth)
  async addPost(
    @Arg('file', () => GraphQLUpload)
    file: FileUpload
  ): Promise<boolean> {
    const { filename, createReadStream } = file;
    console.log(file);
    const uploadTime = new Date().toISOString();
    const pathName = path.join(
      __dirname,
      `../../images/${uploadTime}_${filename}`
    );
    return new Promise((resolve, reject) =>
      createReadStream()
        .pipe(createWriteStream(pathName, { autoClose: true }))
        .on('finish', () => resolve(true))
        .on('error', () => reject(false))
    );
  }

A help will be appreciated. 🙏

@albertcito
Copy link

@aseerkt I have the same problem, finally I use sharp to save the file in the server.

const getBuffer = (file: FileUpload) => {
  const stream = file.createReadStream();
  return new Promise<Buffer>((resolve, reject) => {
    const buffers: Buffer[] = [];
    stream.on('error', (error) => reject(error));
    stream.on('data', (data: Buffer) => buffers.push(data));
    stream.on('end', () => resolve(Buffer.concat(buffers)));
  });
};

const imageBuffer = await getBuffer(file);
await sharp(imageBuffer).toFile(`uploads/images/${image.fileName()}`);

@bautistaaa
Copy link

How can we use with with form data with other fields.

For example:
A form that has an image, title, description

@sittingbool
Copy link

The examples above all gibe me the following error:
Error: There can be only one type named "Upload".
because Apollo has its own upload feature since 2.0. How can I use this in here?

@chinanderm
Copy link

The examples above all gibe me the following error:
Error: There can be only one type named "Upload".
because Apollo has its own upload feature since 2.0. How can I use this in here?

As @hemedani pointed out, try setting uploads: false in your ApolloServer config.

@MoGoethe
Copy link

Please help I am getting "Variable "$file" got invalid value {}; Upload value invalid." when calling mutation from apollo client

import { Mutation, Resolver, Args, Query, FieldResolver, Root, Arg } from 'type-graphql';
import { Person, FindManyWorkerArgs, Position } from 'prisma/generated/type-graphql';
import { PersonService } from '../services/person.service';
import { CreateWorkerArgs, UpdateWorkerArgs } from '../dtos/person.input';
import { Worker, File } from 'prisma/generated/type-graphql'
import { GraphQLUpload, FileUpload } from 'graphql-upload';

import { PaginatedWorker } from '../dtos/paginated-person.type';
import { WorkerService } from '../services/worker.service';
import { PositionService } from 'src/position/services/position.service';
import { Inject } from 'typedi';
import { BadRequestException } from '@nestjs/common';


@Resolver(of => Worker)
export class WorkerResolver {

    constructor(
        private readonly workerService: WorkerService,    
        private readonly personService: PersonService,
        private readonly positionService: PositionService
    ) { }
    
    @Mutation(returns => Worker)
    async createWorker(@Args() args: CreateWorkerArgs, @Arg('file', type => GraphQLUpload, { nullable: true }) file?: FileUpload): Promise<Worker> {
        return this.workerService.createWorker(args.data ,  file);
    }
}

look at this
image
image

@saanny
Copy link

saanny commented Jun 27, 2021

Please help I am getting "Variable "$file" got invalid value {}; Upload value invalid." when calling mutation from apollo client

import { Mutation, Resolver, Args, Query, FieldResolver, Root, Arg } from 'type-graphql';
import { Person, FindManyWorkerArgs, Position } from 'prisma/generated/type-graphql';
import { PersonService } from '../services/person.service';
import { CreateWorkerArgs, UpdateWorkerArgs } from '../dtos/person.input';
import { Worker, File } from 'prisma/generated/type-graphql'
import { GraphQLUpload, FileUpload } from 'graphql-upload';

import { PaginatedWorker } from '../dtos/paginated-person.type';
import { WorkerService } from '../services/worker.service';
import { PositionService } from 'src/position/services/position.service';
import { Inject } from 'typedi';
import { BadRequestException } from '@nestjs/common';


@Resolver(of => Worker)
export class WorkerResolver {

    constructor(
        private readonly workerService: WorkerService,    
        private readonly personService: PersonService,
        private readonly positionService: PositionService
    ) { }
    
    @Mutation(returns => Worker)
    async createWorker(@Args() args: CreateWorkerArgs, @Arg('file', type => GraphQLUpload, { nullable: true }) file?: FileUpload): Promise<Worker> {
        return this.workerService.createWorker(args.data ,  file);
    }
}

look at this
image
image

check this link https://stackoverflow.com/questions/60059940/graphql-apollo-upload-in-nestjs-returns-invalid-value

@edw19
Copy link

edw19 commented Jul 21, 2021

In nextjs, I have this error
image

image

with this config
image

image

what Im doing wrong ?

@sgentile
Copy link

sgentile commented Sep 18, 2021 via email

@SergioSuarezDev
Copy link

SergioSuarezDev commented Nov 4, 2021

Hi guys i have a similar problem when I load images with a resolver in GraphQL using a inputType class in NestJS, I get 0-byte files when i upload to s3.
However, if I add everything as args, the files arrive correctly, I don't see too many differences, what could be the problem?

mutation:

@Mutation(() => Course)
async addResources(
  @Args({ name: 'input', type: () => CreateResources }) input: CreateResources,
  @CurrentUser() user: User
) {
  const type = EXTRARESOURCES;
  const result = await this.courseService.addExtraResources(
    user,
    input,
    type
  );
  if (!result) errorInvalidInput(type);
  return result;
}

inputType files arrive with 0 bytes

@InputType()
export class CreateResources {
  @IsOptional()
  @Field(() => [CreateResourceLinkInput], { nullable: true })
  links: CreateResourceLinkInput[];

  @IsOptional()
  @Field(() => [CreateResourceVideoInput], { nullable: true })
  videos: CreateResourceVideoInput[];

  @IsOptional()
  @Field(() => [GraphQLUpload], { nullable: true })
  documents: Promise<[FileUpload]>;

  @IsOptional()
  @Field(() => [GraphQLUpload], { nullable: true })
  images: Promise<[FileUpload]>;
}

All as args works fine

@Mutation(() => Course)
async AddResources(
  @Args({ name: 'links', type: () => [CreateResourceLinkInput], nullable: true }) links: [CreateResourceLinkInput],
  @Args({ name: 'videos', type: () => [CreateResourceVideoInput], nullable: true }) videos: [CreateResourceVideoInput],
  @Args({ name: 'documents', type: () => [GraphQLUpload], nullable: true }) documents: [FileUpload],
  @Args({ name: 'images', type: () => [GraphQLUpload], nullable: true }) images: [FileUpload],
  @CurrentUser() user: User
) {
  const type =EXTRARESOURCES;
  const input = {
    links,
    videos,
    documents,
    images
  };
  const result = await this.courseService.addExtraResources(
    user,
    input,
    type
  );
  if (!result) errorInvalidInput(type);
  return result;
}

@MichalLytek
Copy link
Owner Author

in NestJS

@SergioSuarezDev So you should ask on Nest github/discord as their implementation is quite different from TypeGraphQL.

@Tjerk-Haaye-Henricus
Copy link

Since apollo-upload-server is deprecated, how can we move on with apollo-upload did anyone find a way dealing with apollo-upload when it comes to file uploads ?

@jdgabriel
Copy link

Packages:

"@apollo/server": "^4.3.0",
"fastify": "^4.11.0",
"@as-integrations/fastify": "^1.2.0",
"graphql-upload-ts": "^2.0.5",

I use graphql-upload-ts for implementarion TypeScript by graphql-upload

Mutation

import { FileUpload, GraphQLUpload } from 'graphql-upload-ts'

@Mutation(() => Boolean)
async uploadService(
 @Arg('picture', () => GraphQLUpload)
 { createReadStream, filename }: FileUpload
) {
 return new Promise(async (resolve, reject) =>
   createReadStream()
     .pipe(createWriteStream(__dirname + '/' + filename))
     .on('finish', () => resolve(true))
     .on('error', () => reject(false)),
 )
}

Receiver error:

{
  "errors": [
    {
      "message": "Variable \"$picture\" got invalid value {}; Upload value invalid.",
      "locations": [
        {
          "line": 1,
          "column": 24
        }
      ],
      "extensions": {
        "code": "BAD_USER_INPUT",
        "stacktrace": [
          "GraphQLError: Variable \"$picture\" got invalid value {}; Upload value invalid.",
          "    at /home/psoeng/www/pso-easynr10/node_modules/graphql/execution/values.js:147:11",
          "    at coerceInputValueImpl (/home/psoeng/www/pso-easynr10/node_modules/graphql/utilities/coerceInputValue.js:154:9)",
          "    at coerceInputValueImpl (/home/psoeng/www/pso-easynr10/node_modules/graphql/utilities/coerceInputValue.js:49:14)",
          "    at coerceInputValue (/home/psoeng/www/pso-easynr10/node_modules/graphql/utilities/coerceInputValue.js:32:10)",
          "    at coerceVariableValues (/home/psoeng/www/pso-easynr10/node_modules/graphql/execution/values.js:132:69)",
          "    at getVariableValues (/home/psoeng/www/pso-easynr10/node_modules/graphql/execution/values.js:45:21)",
          "    at buildExecutionContext (/home/psoeng/www/pso-easynr10/node_modules/graphql/execution/execute.js:280:63)",
          "    at execute (/home/psoeng/www/pso-easynr10/node_modules/graphql/execution/execute.js:116:22)",
          "    at executeIncrementally (/home/psoeng/www/pso-easynr10/node_modules/@apollo/server/src/incrementalDeliveryPolyfill.ts:109:17)",
          "    at processTicksAndRejections (node:internal/process/task_queues:95:5)"
        ]
      }
    }
  ]
}

All solutions posted here, dont work for me.
Any solutions fot this? thx

@jdgabriel
Copy link

@Tjerk-Haaye-Henricus You finded any solutions for your problem?

@Tjerk-Haaye-Henricus
Copy link

Hey @jdgabriel 👋

Yes i found a way. I'm using this combination now:

import GraphQlUpload from 'graphql-upload/GraphQLUpload.mjs'
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'
import Upload from 'graphql-upload/Upload.mjs'


export interface IImage extends IEntity /* This is for our app internal */ {
  alternativeText?: string
  uri?: string
}


@ObjectType()
@InputType('ImageInput')
export class Image extends Entity implements IImage {
  @Field((type) => String, { nullable: true })
  alternativeText?: string
}


export class FileResolver {
  @Mutation(() => Image)
  async uploadFile(
    @Arg('file', () => GraphQlUpload)
    { createReadStream, filename }: Upload,
  ) {
    try {
      const key = filename || 'create_random_string_or_estimate_a_cool_filename'

      const result = await sendToS3({ key, body: createReadStream() })
      logger.info('uploadFile.result', result)
      return { id: result.Key }
    } catch (error) {
      logger.error('API.error', (error as Error).message)
      return false
    }
  }
}


// Build Schema with executor


app.use(
  graphqlUploadExpress({
    maxFileSize: SERVER.MAX_UPLOAD_FILE_SIZE,
    maxFiles: SERVER.MAX_FILES,
  }),
)

I hope that i got all things covered :D

@jdgabriel
Copy link

@Tjerk-Haaye-Henricus, thanks for the answer.
With the changes you suggested, I'm still having the same problem.

GraphQLError: "Variable \"$picture\" got invalid value { path: \"myDoc.docx\" }; Upload value invalid."

Packages:

"@apollo/server": "^4.3.0",
"fastify": "^4.11.0",
"@as-integrations/fastify": "^1.2.0",
"graphql-upload": "^16.0.2"

I'm a beginner in graphql, I can't find solutions.

@jdgabriel
Copy link

@Tjerk-Haaye-Henricus, thanks for the answer. With the changes you suggested, I'm still having the same problem.

GraphQLError: "Variable \"$picture\" got invalid value { path: \"myDoc.docx\" }; Upload value invalid."

Packages:

"@apollo/server": "^4.3.0",
"fastify": "^4.11.0",
"@as-integrations/fastify": "^1.2.0",
"graphql-upload": "^16.0.2"

I'm a beginner in graphql, I can't find solutions.


Solve my problem with fastify:

Packages:

"fastify": "^4.11.0",
"@fastify/multipart": "^7.4.0",
"graphql-upload-ts": "^2.0.5",
import multiPart from "@fastify/multipart"
import { processRequest } from "graphql-upload-ts"

--
// server.ts
await server.register(multiPart) // Add Multipart plugin

server.addHook('preValidation', async (request, reply) => {
  if (!request.isMultipart()) return // Valid if is multipart header

// Process all Files
  request.body = await processRequest(request.raw, reply.raw, {
    maxFileSize: 1e20, // Max file size example, change it
    maxFiles: 20,  // Max files example, change it
  })
})
// Resolver

import { FileUpload, GraphQLUpload  } from 'graphql-upload-ts'

-- 

@Mutation(() => Boolean)
async addProfilePicture(
  @Arg('picture', () => [GraphQLUpload]) picture: FileUpload[],
) {
  try {
    picture.map(async (pic) => {
      const { filename, createReadStream } = await pic
      return new Promise((resolve, reject) =>
        createReadStream()
          .pipe(createWriteStream(`./public/${filename}`))
          .on('finish', () => resolve(true))
          .on('error', () => reject(false)),
      )
    })
    return true
  } catch (error) {
    return false
  }
}

@danielfigueroajps
Copy link

import { processRequest } from "graphql-upload-ts"

@jdgabriel Can you share all your server.ts content?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement 🆕 New feature or request
Projects
None yet
Development

No branches or pull requests