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

Cannot upload file: POST body missing, invalid Content-Type, or JSON object has no keys. #46

Closed
SkipEveryLunch opened this issue Feb 26, 2023 · 1 comment
Labels
question Further information is requested

Comments

@SkipEveryLunch
Copy link

SkipEveryLunch commented Feb 26, 2023

Problem

Hi @MichalLytek ,
I tried to make a GraphQL endpoint which accepts a text file in a NestJS app. However, I got the following error when I actually uploaded a text file to the endpoint via Altair:

"POST body missing, invalid Content-Type, or JSON object has no keys."

I'd like to solve this problem. If you shed some lights on it, I would really appreciate it.
(And I'm sorry in advance if I overlook something obvious)

Expected behavior

It's suppose to return the content of text file.

How to reproduce

The query I sent from Altair is the following:

mutation uploadFile($file: Upload!){
  uploadFile(file: $file){
    message
  }
}

I sent the above query, setting Content-type header to application/octet-stream.
When I set it to application/json, the error message changed:

{
  "statusCode": 400,
  "message": "Unexpected token - in JSON at position 0",
  "error": "Bad Request"
}

The resolver I made was the following:

import { Query, Resolver, Mutation, Args} from 'type-graphql';
import { UploadResponse, UploadInputArgs } from './types';
import { Readable } from 'stream';

@Resolver()
// you have to make at lease 1 query in a NestJS app
export class UploadResolver {
  @Query((returns)=>UploadResponse)
  getHello(): UploadResponse{
    return {
      message: 'hello'
    }
  }
// this endpoint does not work
  @Mutation((returns)=>UploadResponse)
  async importFile(
    @Args() args: UploadInputArgs
  ): Promise<any>{
    const file = await args.file
    const {createReadStream} = file
    const buffer = await this.streamToBuffer(createReadStream())
    return {
      message:buffer.toString()
    };
  }
  async streamToBuffer(stream: Readable): Promise<Buffer> {
    const buffer: Uint8Array[] = [];
    return new Promise((resolve, reject) =>
      stream
        .on('error', (error) => reject(error))
        .on('data', (data) => buffer.push(data))
        .on('end', () => resolve(Buffer.concat(buffer))),
    );
  }
}

The content of type.ts is the following.

import { ObjectType, Field, ArgsType } from "type-graphql";
import { GraphQLUpload } from "graphql-upload-minimal";
import { Readable } from 'stream';

@ObjectType()
export class UploadResponse {
  @Field(() => String)
  message!: String;
}

@ArgsType()
export class UploadInputArgs{
  @Field(() => GraphQLUpload)
  file!: Promise<Upload>;
}

class Upload{
  fieldName: string;
  filename: string;
  mimetype: string;
  encoding: string;
  createReadStream: ()=> Readable;
}

The whole (super small) repository is the following, for what it's worth.
nestjs-typegraphql-upload

What I tried to solve the problem

I made it sure that I could upload a file to a endpoint, if I use @nestjs/graphql instead of type-graphql. So I think it's due to type-graphql or typegraphql-nestjs.

Environment

I use M1 macbook and node v16.14.0, run the app in local(without using Docker).
The whole dependencies in package.json is the following:

  "dependencies": {
    "@apollo/subgraph": "^2.3.2",
    "@nestjs/apollo": "^10.2.0",
    "@nestjs/common": "^9.0.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/graphql": "^10.2.0",
    "@nestjs/platform-express": "^9.0.0",
    "apollo-server-core": "^3.11.1",
    "apollo-server-express": "^3.11.1",
    "graphql": "^16.6.0",
    "graphql-upload-minimal": "^1.5.4",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.2.0",
    "type-graphql": "2.0.0-beta.1",
    "typegraphql-nestjs": "^0.5.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.0.0",
    "@nestjs/schematics": "^9.0.0",
    "@nestjs/testing": "^9.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "29.2.4",
    "@types/node": "18.11.18",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "29.3.1",
    "prettier": "^2.3.2",
    "source-map-support": "^0.5.20",
    "supertest": "^6.1.3",
    "ts-jest": "29.0.3",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "4.1.1",
    "typescript": "^4.7.4"
  },
@SkipEveryLunch
Copy link
Author

Problem solved by applying graphqlUploadExpress middleware.

export default class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(graphqlUploadExpress()).forRoutes("graphql");
  }
}

@MichalLytek MichalLytek added the question Further information is requested label Feb 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants