type-graphql
type-graphql copied to clipboard
File upload
Integration with https://github.com/jaydenseric/apollo-upload-server
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
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> {
Thanks @19majkel94 it works
@19majkel94 where do you import TS type Upload from?
@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.
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.
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 containingdeclare 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?
@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.
@19majkel94 thank you for replying.
@daviddlv have you a example? for upload with scalar on type-graphql?
@johinsDev , here an example of my resolver : https://gist.github.com/daviddlv/0259ce8b64a3e23611a96b0c52c27a27
@johinsDev , here an example of my resolver : https://gist.github.com/daviddlv/0259ce8b64a3e23611a96b0c52c27a27
thanks, its cool. but what have you FileInput?
I put the FileInput file in the gist
@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;
@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.
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
@sgtkuncoro
provide explicit type
@Arg("xarr", type => [XArg]) xarr
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
}
}
}
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?
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.
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?
Apollo Server version? Apolo + express, + koa ...?
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} />;
};
@naishe have you added the express middleware?
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
}
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)
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.
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
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));
});
}
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