Implementing file uploads using graphql-upload library, integrating it with apollo-server-fastify, and taking advantage of the code-first

Is there a correct way to implement file uploads from a client to a server using the following combination of packages/techniques (along with their corresponding dependencies not listed):

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

We are aiming to implement the functionality described in the "apollo server file uploads" documentation, specifically using the NestJS GraphQL Code First technique but with support for multiple files instead of just one.

To achieve this, we can use the following code:
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}`);
});

Currently, my frontend successfully sends the files to the server using this mutation:

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

If you would like to see examples of how the requests are sent along with the expected file binaries:

  • link1
  • link2

Answer â„–1

Integrating graphql-upload with Apollo Server and NestJS's @nestjs/platform-fastify allows you to handle file uploads from the client side efficiently.

To begin, update your NestJS's main.ts file by integrating 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();

Afterwards, define the Upload scalar in your GraphQL Module and update your resolvers and schema accordingly. Below is an example of how you can achieve this:

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 your own 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 specify the 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, we are utilizing Fastify as our HTTP server instead of the default one (Express) and graphql-upload middleware to handle file uploads. The middleware parses the incoming requests with file upload and passes the streams into your resolvers. Note that in the @Mutation decorator, we use GraphQLUpload provided by graphql-upload, which is the required format for file uploads.

Make sure you have all the necessary packages installed. If not, install them using npm or yarn:

npm install @nestjs/graphql graphql-tools graphql apollo-server-fastify fastify multipart graphql-upload

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using React Material UI icon within an auto complete feature

https://i.stack.imgur.com/W3CmF.png I am struggling to incorporate the target icon into the autoComplete component. After reviewing the documentation, I have been unable to find a solution. In TextInput, a similar outcome can be achieved by implementing ...

Tips for resizing the MUI-card on a smaller screen

Is there a way to adjust the width of the card on small screen sizes? It appears too small. You can view my recreation on codesandbox here: https://codesandbox.io/s/nameless-darkness-d8tsq9?file=/demo.js The width seems inadequate for this particular scr ...

Issue with handleClick function in React.js and Material UI not functioning as expected

Having an issue integrating react.js with material-ui as my event handlers do not seem to be functioning properly. Experimented with the boilerplate material js file, which can be found at: (https://github.com/mui-org/material-ui/blob/master/docs/src/page ...

Guide to storing publicUrl from product form in NextJS using Supabase

Seeking assistance as a newcomer to Supabase, I find myself struggling with certain concepts. My goal is to design a basic form where I can upload an image, input name, description, and price. Afterwards, the image should be saved in a bucket while the in ...

Unpacking the information in React

My goal is to destructure coinsData so I can access the id globally and iterate through the data elsewhere. However, I am facing an issue with TypeScript on exporting CoinProvider: Type '({ children }: { children?: ReactNode; }) => void' is no ...

Having trouble using Node-fetch with Electron 11? Keep getting an error message that says resp.body.pipe is not a function

Currently, I am attempting to utilize node-fetch in order to download a zip file from a specific URL. However, I keep encountering this error message: TypeError: res.body.pipe is not a function After upgrading my Electron app version from "electron&q ...

React JS component experiencing issues with Material UI modal functionality

Attempting to reproduce the material ui modal example has proven to be a challenge for me. Initially, I encountered an error stating "Cannot read property 'setState' of undefined" which I managed to resolve. However, even after resolving this iss ...

Exploring the difference between using import global and getStaticProps in a Next.js/React

As a newcomer to Next.js, I have a question regarding importing JSON files in my index.js file located in the pages directory. I have a component that will display a list from a JSON object, and I'm curious about the difference between importing this ...

user interface grid element in Materia

After writing this code, I encountered the following error: No overload matches this call. Overload 1 of 2, '(props: { component: ElementType<any>; } & SystemProps<Theme> & { children?: ReactNode; classes?: Partial<GridClasses>; .. ...

create-react-app insert personalized configuration variable

I am looking to incorporate custom environment variables into my React project using a boilerplate setup. Within my development (DEV) environment, I would like to direct the API towards my localhost backend, while in production (PROD) environment, it shoul ...

Is it possible to change Material UI DateRange Picker to a DateTimeRange Picker?

Thinking about getting the Material UI-X commercial license as I really need to use the DateRange picker component. However, it's important for me to be able to select a time range as well, and especially within the same day. Can the current state of ...

CSS does not have the capability to style child elements

Having trouble changing the text for child elements when applying CSS classes to parent elements. Is there a specific reason why this is happening? Take a look at my current code: <Box //not affecting all child elements ...

One can only iterate through the type 'HTMLCollection' by utilizing the '--downlevelIteration' flag or setting a '--target' of 'es2015' or above

I'm currently working on developing a loader for my static grid. I've incorporated the react-shimmer-skeleton package source code, but I'm encountering issues with eslint in strict mode. You can find the respective repository file by followi ...

Solving the Problem of Card Spacing in React Material UI

I am currently incorporating Material UI into a react application. Utilizing Material UI component cards, I have encountered an issue where unwanted white space appears on the right edge of the page. My objective is to introduce gaps between "neighboring" ...

Ways to display a checked or unchecked checkbox in react depending on a boolean value obtained from an API

Is there a way to display a checked or unchecked checkbox in react jsx depending on a boolean value retrieved from an api? ...

Explore how Next.js's getServerSideProps feature incorporates loading animations and improves

I have implemented getServerSideProps in my project's pages/post/index.js file: import React from "react"; import Layout from "../../components/Layout"; function Post({ post }) { console.log("in render", post); return ( <Layout title={pos ...

How does a database contribute to the functionality of a redux application?

Recently, I started exploring redux and finally understood the architecture behind it. In a single page redux application where all data is stored in a central store and updated through frontend actions, what purpose does the back-end and database serve? ...

Optimizing your webpack bundle by dynamically splitting directories with the SplitChunks plugin

In an attempt to organize my React code, which was created using create-react-app, I am utilizing the splitChunks plugin in the following manner: The current structure of my components (JSX) is as follows: services serviceA ...

What is the reason for TypeScript not displaying a type mismatch error in this particular instance?

After starting to use React with TypeScript, I defined my types as follows: type CardInfo = { cardIndex: null | number; title: string; note: string; _id: string; from: string; cardId: string; }; type ContentType = { title: string; note: st ...

Optimize React Material UI: Stop unnecessary re-rendering of child component Tabs when switching tabs

I have multiple "tasks" with individual sets of tabs that I want to organize. Each tab within the TabPanel contains a TaskOrg component responsible for fetching data from the backend through API calls and displaying it in a DataGrid. The issue arises when ...