nest icon indicating copy to clipboard operation
nest copied to clipboard

feat(platform): add multer support for fastify platform

Open gperdomor opened this issue 4 years ago β€’ 23 comments

PR Checklist

Please check if your PR fulfills the following requirements:

  • [x] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
  • [x] Tests for the changes have been added (for bug fixes / features)
  • [x] Docs have been added / updated (for bug fixes / features)

PR Type

What kind of change does this PR introduce?

[ ] Bugfix
[x] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Other... Please describe:

What is the current behavior?

File upload using Multer is only supported for express adapter

Issue Number: N/A

What is the new behavior?

Add fastify-multer support in @nestjs/platform-fastify.

Does this PR introduce a breaking change?

[ ] Yes
[x] No

Other information

gperdomor avatar Jun 18 '20 04:06 gperdomor

Pull Request Test Coverage Report for Build c9974085-88e3-4546-b56a-9322d1eed8b8

  • 93 of 95 (97.89%) changed or added relevant lines in 9 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.06%) to 94.79%

Changes Missing Coverage Covered Lines Changed/Added Lines %
packages/platform-fastify/multer/interceptors/file-fields.interceptor.ts 15 17 88.24%
<!-- Total: 93 95
Totals Coverage Status
Change from base Build 539a93a9-8db4-434e-944d-b98d10267b0b: 0.06%
Covered Lines: 4931
Relevant Lines: 5202

πŸ’› - Coveralls

coveralls avatar Jun 18 '20 04:06 coveralls

I am looking forward to using this feature <3

smhmayboudi avatar Jul 03 '20 11:07 smhmayboudi

You beat me to it! I was doing literally the exact same thing today! Good stuff, hope this gets merged soon

eugenio165 avatar Jul 15 '20 17:07 eugenio165

@kamilmysliwiec any thoughts on PR? #2088 was closed due to fastify-multer being a too young library, but a year has passed since then

asc11cat avatar Jul 18 '20 12:07 asc11cat

Any update on this pull? I think it should accepted @kamilmysliwiec

AliYusuf95 avatar Aug 02 '20 16:08 AliYusuf95

Should we consider adding this support through the official core https://github.com/fastify/fastify-multipart package, instead of fastify-multer from the community?

This feature would be configured and used differently than multer for Express, but is it an issue?

@kamilmysliwiec what are your thoughts on that?

emagnier avatar Sep 09 '20 20:09 emagnier

Is there a timeline for this feature? What is currently the best way to use the file upload with fastify in nestjs? The only blocker we have, which blocks the migration to nestjs with fastify.

MatznRisto avatar Feb 15 '21 14:02 MatznRisto

Is there a timeline for this feature? What is currently the best way to use the file upload with fastify in nestjs? The only blocker we have, which blocks the migration to nestjs with fastify.

Same here, do you guys have a timeline?

newradius avatar Mar 19 '21 16:03 newradius

May I ask what is the status of this pull request? File upload is an essential part of our app and since we would like to use Fastify, the availability of an officially supported FileInterceptor is key.

ghost avatar May 12 '21 12:05 ghost

Any news on this?

newradius avatar Jul 08 '21 10:07 newradius

Also wondering what’s the status ?

kscerbiakas avatar Jul 11 '21 11:07 kscerbiakas

Would also like to know what needs to happen to make this PR to be merged

MickL avatar Jul 23 '21 16:07 MickL

@kamilmysliwiec any chance of this getting merged? Is it just a case to fix merge conflicts?

thebergamo avatar Aug 25 '21 22:08 thebergamo

Because if its long time not merged, then maybe package it up as a package for others to use? Because there is no response from @kamilmysliwiec and there are quite a few people who'd like to use it. What do you think @gperdomor ?

kscerbiakas avatar Sep 14 '21 10:09 kscerbiakas

I had to revert back a project from fastify to express because multer integration was too complex / would have taken too much time :(

MickL avatar Sep 14 '21 11:09 MickL

Because if its long time not merged, then maybe package it up as a package for others to use? Because there is no response from @kamilmysliwiec and there are quite a few people who'd like to use it. What do you think @gperdomor ?

A new MR is in progress: https://github.com/nestjs/nest/pull/6935

gperdomor avatar Sep 14 '21 13:09 gperdomor

For those who are stuck in this issues with mulher I found a way to workaround it fastify-multer and it worked quite fine.

thebergamo avatar Sep 14 '21 16:09 thebergamo

any progress on this?

vytautas-pranskunas- avatar Apr 10 '22 09:04 vytautas-pranskunas-

@kamilmysliwiec Any updates on this pull request?

EvilCheetah avatar Aug 04 '22 20:08 EvilCheetah

Any updates on this pull request?

@EvilCheetah If you need this, I've created @nest-lab/fastify-multer as an intermediary and to allow for file uploads with fastify as the HTTP adapter. Almost identical API except that the FastifyMulterModule must be imported at least once to ensure that the body parser for multipart/form-data gets registered

jmcdo29 avatar Aug 05 '22 03:08 jmcdo29

@jmcdo29 thank you for creating this package!

Why is this MR not getting the attention as it should? 2 years passed since the MR and it still didn't get merge.

necm1 avatar Aug 05 '22 09:08 necm1

Because it relies on a tiny package that is out of our control.

kamilmysliwiec avatar Aug 05 '22 09:08 kamilmysliwiec

Since unfortunatelly this PR hasn't been merged and all the current solutions I found in the web don't fit my needs or are unstable, I decided to create my own solution based on Interceptors and '@fastify/multipart`. I leave the code here just in case someone came here in the future :)

main.ts

 import multipart from '@fastify/multipart';

  const fastify = new FastifyAdapter(fastifyOptions);
  fastify.register(multipart as FastifyPluginCallback<any, any>);

  const app = await NestFactory.create<NestFastifyApplication>(AppModule, fastify);

UploadService.ts | NOTE: The localstack variables are just for testing, you can use localstack or a production S3.

import { Injectable } from '@nestjs/common';
import { S3Client, DeleteObjectCommand, PutObjectCommandInputType } from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
import dotenv from 'dotenv';

dotenv.config();

const s3 = new S3Client({
  region: process.env.AWS_REGION,
  endpoint: process.env.LOCALSTACK_S3_ENDPOINT,
  forcePathStyle: !!process.env.LOCALSTACK_S3_ENDPOINT,
});

@Injectable()
export class UploadService {
  async upload(stream: PutObjectCommandInputType['Body'], name: string, prefix: string) {
    return new Upload({
      client: s3,
      params: {
        Bucket: process.env.BUCKET,
        Key: name,
        Body: stream,
      },
    }).done() as Promise<{
      Location: string;
      Key: string;
      Bucket: string;
      ETag: string;
    }>;
  }

  async remove(key: string) {
    return s3.send(
      new DeleteObjectCommand({
        Bucket: process.env.BUCKET,
        Key: key,
      })
    );
  }
}

UploadInterceptor.ts

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Writable } from 'stream';
import pump from 'pump';
import type { UploadService } from '../modules/upload/upload.service';

@Injectable()
export class UploadInterceptor implements NestInterceptor {
  names: string[] | string;

  prefix: string;

  constructor(private uploadService: UploadService, names: string[] | string, prefix: string) {
    this.names = names;
    this.prefix = prefix;
  }

  async intercept(context: ExecutionContext, next: CallHandler) {
    const req = context.switchToHttp().getRequest();

    req.fileStorage = {};
    req.body = req.body || {};

    const parts = req.parts();

    for await (const part of parts) {
      if (part.type !== 'file') {
        req.body[part.fieldname] = part.value;
        continue;
      }

      if (!this.names.includes(part.fieldname) && this.names !== '*') {
        // If there is a file that is not in the list of allowed files, pipe the stream to nowhere so the loop doesn't hang.
        const writable = new Writable();
        writable._write = (_, __, _next) => _next();
        pump(part.file, writable);
        continue;
      }

      const newFile = await this.uploadService.upload(part.file, part.filename, this.prefix);

      req.fileStorage[part.fieldname] = req.fileStorage[part.fieldname] || [];

      req.fileStorage[part.fieldname].push({
        fieldname: part.fieldname,
        filename: part.filename,
        encoding: part.encoding,
        mimetype: part.mimetype,
        location: newFile.Location,
        key: newFile.Key,
        bucket: newFile.Bucket,
        etag: newFile.ETag,
      });
    }

    return next.handle();
  }
}

ExampleController.ts

  @UseInterceptors(new UploadInterceptor(new UploadService(), '*', 'prefix'))
  async create@Req() req: FastifyRequest): Promise<GroupModel> {
    console.log(req.fileStorage)
  }

fpmanuel avatar Apr 29 '23 09:04 fpmanuel