MichalLytek / type-graphql

Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
https://typegraphql.com
MIT License
8.04k stars 678 forks source link

File upload #37

Open MichalLytek opened 6 years ago

MichalLytek commented 6 years ago

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

daviddlv commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

Thanks @19majkel94 it works

danpaugo commented 6 years ago

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

MichalLytek commented 6 years ago

@danpaugo I've found some types definition in github issue: https://github.com/jaydenseric/apollo-upload-server/issues/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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

@19majkel94 thank you for replying.

johinsDev commented 5 years ago

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

daviddlv commented 5 years ago

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

johinsDev commented 5 years ago

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

thanks, its cool. but what have you FileInput?

daviddlv commented 5 years ago

I put the FileInput file in the gist

laukaichung commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

@sgtkuncoro

provide explicit type

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

andfk commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 4 years ago

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 commented 4 years ago

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

naishe commented 4 years ago

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 0=@wsebr3bveoy31.png
{
"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 commented 4 years ago

@naishe have you added the express middleware?

naishe commented 4 years ago

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 
}
MichalLytek commented 4 years ago

Yes, apollo server has this built-in. For other cases, you need to apply the upload middleware:

express()
  .use(
    '/graphql',
    graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }),
    graphqlHTTP({ schema })
  )
  .listen(3000)
hemedani commented 4 years ago

apollo-server-express use the version 8 of graphql-upload and it not support node version 13 throwing this error Maximum call stack size exceeded this error solved in graphql-upload in this issues but when we install the new version of graphql-upload the server response with throwing got invalid value {}; Expected type Upload. Upload value invalid.

hemedani commented 4 years ago

when i host graphql-upload on monorepo for using the right version of that type-graphql throwing this error : Cannot determine GraphQL input type for image

hemedani commented 4 years ago

I solved this with disabling apollo-server upload property :

    const apolloServer = new ApolloServer({
        schema,
        context: ({ req }) => ({ req }),
        introspection: true,
        uploads: false // disable apollo upload property
    });

install the new version of graphql-upload : yarn add graphql-upload

Setup the graphql-upload middleware.

const app = Express();
app.use(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));
apolloServer.applyMiddleware({
        app
    });

and then setup the upload mutation :

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

@Resolver()
export class UploadResolver {
  @Mutation(_ => Promise<Boolean>)
  async singleUpload(@Arg('file', () => GraphQLUpload) file: FileUpload) {
    const { createReadStream, filename } = await file;
    const writableStream = createWriteStream(
            `${__dirname}/../../../files/images/${filename}`,
            { autoClose: true }
        );
    return new Promise((res, rej) => {
            createReadStream()
                .pipe(writableStream)
                .on("finish", () => res(true))
                .on("error", () => rej(false));
        });
}
andersoncscz commented 4 years ago

I solved this with disabling apollo-server upload property :

    const apolloServer = new ApolloServer({
        schema,
        context: ({ req }) => ({ req }),
        introspection: true,
        uploads: false // disable apollo upload property
    });

install the new version of graphql-upload : yarn add graphql-upload

Setup the graphql-upload middleware.

const app = Express();
app.use(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));
apolloServer.applyMiddleware({
        app
    });

and then setup the upload mutation :

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

@Resolver()
export class UploadResolver {
  @Mutation(_ => Promise<Boolean>)
  async singleUpload(@Arg('file', () => GraphQLUpload) file: FileUpload) {
    const { createReadStream, filename } = await file;
    const writableStream = createWriteStream(
            `${__dirname}/../../../files/images/${filename}`,
            { autoClose: true }
        );
    return new Promise((res, rej) => {
            createReadStream()
                .pipe(writableStream)
                .on("finish", () => res(true))
                .on("error", () => rej(false));
        });
}

This solution worked for me, make sure you guys have created the folder where the files will be uploaded, otherwise you'll get an error

theerfan commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

JeremyBernier commented 4 years ago

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 commented 4 years ago

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 commented 3 years ago

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. :pray:

albertcito commented 3 years ago

@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 commented 3 years ago

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

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

sittingbool commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

In nextjs, I have this error image

image

with this config image

image

what Im doing wrong ?

sgentile commented 3 years ago

I suspect your problem is around your read and write streams, I don't think it has to do with typegraphql and uploading files ?

I read the files in as follows - but I write to S3 bucket (setting the body using createReadStream() which doesn't really help you for that). I'd look into making sure you're using the create read/write streams correctly. Some snippets from how I do it:

@Arg('files', () => GraphQLUpload) files: FileUpload[]

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

const readableStreams = await Promise.all(files);

const pipedStreams = readableStreams.map((readStreamInstance) => { const { filename, createReadStream } = readStreamInstance;

On Fri, Sep 17, 2021 at 5:16 AM SedkiDataBank @.***> wrote:

I am getting an unresolved error, anyone can take a look? tyvm

{ "errors": [ { "message": "Unexpected error value: false", "locations": [ { "line": 2, "column": 3 } ], "path": [ "addCandidateResume" ], "extensions": { "code": "INTERNAL_SERVER_ERROR", "exception": { "stacktrace": [ "Error: Unexpected error value: false", " at asErrorInstance (/Users/sed9ydatabank/Documents/real-deal/ovice-rms/ovice-rms-server/node_modules/graphql/execution/execute.js:516:10)", " at processTicksAndRejections (internal/process/task_queues.js:95:5)" ] } } } ], "data": null }

import { Resolver, Mutation, Arg } from "type-graphql";import { GraphQLUpload, FileUpload } from "graphql-upload";import { createWriteStream } from "fs";

@Resolver()export class CandidateResumeResolver { @Mutation(() => Boolean) async addCandidateResume( @Arg("resume", () => GraphQLUpload) { createReadStream, filename }: FileUpload ): Promise { return new Promise(async (resolve, reject) => createReadStream() .pipe(createWriteStream(__dirname + /../../../resumes/${filename})) .on("finish", () => { resolve(true); }) .on("error", () => reject(false)) ); }}

const apolloServer = new ApolloServer({ schema, uploads: false });

app.use(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));

I have created a resumes folder but I am getting this error.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/MichalLytek/type-graphql/issues/37#issuecomment-921642395, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABAKNFBF5PKIJKSO6TR2PLUCMBPTANCNFSM4ETHD66Q . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

SergioSuarezDev commented 3 years ago

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 commented 3 years ago

in NestJS

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

Tjerk-Haaye-Henricus commented 2 years ago

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 commented 1 year ago

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 commented 1 year ago

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