File uploads with graphql-upload, apollo-server-fastify, and the NestJS code first approach
Asked Answered
R

1

14

What is the correct implementation for receiving file uploads from a client on a server utilizing the following combination of packages/techniques (and their corresponding dependencies not listed):

  • graphql-upload
  • apollo-server-fastify
  • @nestjs/platform-fastify (code first approach)

Ideally, we are trying to implement the following (from apollo server file uploads) using the NestJS GraphQL Code First technique (except for multiple files instead of just one)

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type File {
    filename: String!
    mimetype: String!
    encoding: String!
  }

  type Query {
    uploads: [File]
  }

  type Mutation {
    singleUpload(file: Upload!): File!
  }
`;

const resolvers = {
  Query: {
    uploads: (parent, args) => {},
  },
  Mutation: {
    singleUpload: (parent, args) => {
      return args.file.then(file => {
        //Contents of Upload scalar: https://github.com/jaydenseric/graphql-upload#class-graphqlupload
        //file.stream is a node stream that contains the contents of the uploaded file
        //node stream api: https://nodejs.org/api/stream.html
        return file;
      });
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

Right now, my frontend is correctly sending the files to my server using this mutation:

gql`
  mutation uploadObjects($files: [Upload!]!) {
    uploadObjects(files: $files) {
      bucketKey
    }
  }
`;

Here are links to images of the request being sent with the file binaries as expected:

Ramberg answered 5/11, 2019 at 23:10 Comment(0)
C
0

You can use graphql-upload with Apollo Server and NestJS's @nestjs/platform-fastify. The idea is to use the graphql-upload as a middleware to handle file uploads from the client side.

First, you need to update the NestJS's main.ts file to integrate the graphql-upload with Apollo and Fastify.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { graphqlUploadFastify } from 'graphql-upload';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );

  // Apply the graphql upload middleware
  app.register(graphqlUploadFastify, {
    maxFileSize: 10000000, // 10 MB
    maxFiles: 5,
  });

  await app.listen(3000);
}
bootstrap();

Then, you need to define the Upload scalar in your GraphQL Module and update your resolvers and schema to use the Upload scalar. Here is an example of how you could do that.

In your app.module.ts:

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      uploads: false, // Disable the built-in upload functionality of Apollo Server
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

Next, define your schema (e.g., upload.schema.ts):

import { ObjectType, Field, InputType, Directive } from '@nestjs/graphql';

@ObjectType()
export class File {
  @Field()
  filename: string;

  @Field()
  mimetype: string;

  @Field()
  encoding: string;
}

@InputType()
@Directive('@extends')
@Directive('@key(fields: "id")')
export class Upload {
  @Field()
  id: string;
}

And the corresponding resolvers (e.g., upload.resolver.ts):

import { Resolver, Mutation, Args, Query } from '@nestjs/graphql';
import { FileUpload, GraphQLUpload } from 'graphql-upload';
import { createWriteStream } from 'fs';
import { File } from './upload.schema';

@Resolver()
export class UploadResolver {
  @Query(() => [File])
  async uploads(): Promise<File[]> {
    // Implement the logic to return the files
    return [];
  }

  @Mutation(() => Boolean)
  async uploadObjects(@Args({ name: 'files', type: () => [GraphQLUpload] }) files: FileUpload[]): Promise<boolean> {
    await Promise.all(
      files.map(async file => {
        const { createReadStream, filename } = await file;
        // Create a stream to your file destination.
        // Here is an example of saving the file locally
        return new Promise((resolve, reject) =>
          createReadStream()
            .pipe(createWriteStream(`./uploads/${filename}`))
            .on('finish', () => resolve(true))
            .on('error', (error) => reject(error)),
        );
      }),
    );

    return true;
  }
}

In this implementation, you are using Fastify as a HTTP server instead of the default one (Express) and graphql-upload middleware to handle the file upload. The middleware parses the incoming requests with file upload and passes the streams into your resolvers. Note that in the @Mutation decorator, we are using GraphQLUpload provided by graphql-upload, which is the required format for file uploads.

Please ensure that all the necessary packages are installed. If not, install them using npm or yarn:

npm install @nestjs/graphql graphql-tools graphql apollo-server-fastify fastify multipart graphql-upload
Cenesthesia answered 1/7, 2023 at 9:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.