Uploading files with NestJS and GraphQL

Uploading files with GraphQL has long been relatively hard and until recently you actually had to resort to good old REST endpoints to take care of your multipart/form data if you didnt prefer doing some hacky workaround. However with the release of Apollo server 2.0 file uploads are now also truly available with GraphQL. The information out there on how to make a working file upload resolver with NestJS and GraphQL is hard to find and those I found were not complete or lacking in some other way. This inspired me to create this post.

Step 1 - Creating a new Scalar Type

GraphQL comes with a couple built in Scalar types, namely Int, Float, String, Boolean and ID. There is no Upload or File type out of the box. Therefore the first step for us is to create this scalar.

Scalar type code

Upload.scalar.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Scalar } from "@nestjs/graphql";
import { GraphQLUpload } from 'graphql-upload';

@Scalar('Upload')
export class Upload {
description = 'File upload scalar type'

parseValue(value){
return GraphQLUpload.parseValue(value);
}

serialize(value){
return GraphQLUpload.serialize(value);
}

parseLiteral(ast){
return GraphQLUpload.parseLiteral(ast, ast.value);
}
}

The code above creates a Scalar of type Upload. Name it whatever you want and save the file.
To use the scalar you simply add it to the providers array in the modules where you want to use it.

Add Scalar to Module file example

app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UserModule } from './user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { typeOrmConfig } from './../config/typeorm.config';
import { Upload } from './Scalars/upload.scalar';

@Module({
imports: [
TypeOrmModule.forRoot(typeOrmConfig),
GraphQLModule.forRoot({
debug: false,
autoSchemaFile: true,
uploads: {
maxFileSize: 20000000, // 20 MB
maxFiles: 5
}
}),
UserModule
],
providers: [Upload]
})
export class AppModule {}

Now that you’ve included the scalar in the modules where you want to use it you should create an schema input type for the resolver(s) where you plan to receive the file(s).
In NestJS there are different ways to do this depending on if you are using the Schema First approach or like me using the Code First approach. I am going to show an example of how this might look like with the code first approach. However if you are using the Schema first approach just create a new schema input type like you normally would in your .graphql SDL file(s).

Code first Schema InputType example

user.input.ts

1
2
3
4
5
6
7
8
import { Upload } from '../Scalars/upload.scalar';
import { InputType, Field } from "@nestjs/graphql";

@InputType()
export class UploadUserProfilePicInput {
@Field()
file : Upload
}

Alright! Almost finished now. Keep in mind that the file where you keep your InputTypes do not matter, obviously mine is named user.input.ts but yours is probably not. Next up, creating and preparing your Resolver for receiving files.

Resolver example

1
2
3
4
5
@Mutation(returns => UserUploadProfilePicType)
public async uploadProfilePic(@Args('UploadUserProfilePicInput') {file} : UploadUserProfilePicInput){
const fileData = await file;
///Do something with the fileData
}

The fileData will contain filename, mimetype, createReadStream. Please keep in mind that the file will be an asyncronous function so you need to await the result or use classic promises syntax before you can use it. If you upload more than one file in the request the file variable will be an array with promises.
In case you wonder what the UserUploadProfilePicType is its just a normal object type, in my case it looks like this:

user.type.ts

1
2
3
4
5
@ObjectType('UserUploadProfilePicType')
export class UserUploadProfilePicType {
@Field()
success : boolean;
}

I just return to the user if the upload of the file(s) has succeeded or not.
Ok, great. Now your graphql server should be ready to receive file uploads from your frontend.
In case you get any errors please keep reading below.

Potential errors and fixes

When I first implemented this I received an error when I tried to upload any file. My error looked like this:

1
2
[Nest] 20188   - 05/09/2020, 03:04:37   [ExceptionsHandler] Promise resolver undefined is not a function +7537ms
TypeError: Promise resolver undefined is not a function

This turned out to be a problem with the class-transformer library that I was using. I do not know what is actually causing the error as I have not looked it up any further, however a quick and easy fix is to use the @Exclude() decorator on the upload field on the InputType() that you previously created for the resolver. Like this:

1
2
3
4
5
6
@InputType()
export class UploadUserProfilePicInput {
@Field()
@Exclude()
file : Upload
}