nest
nest copied to clipboard
feat(platform): add multer support for fastify platform
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
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 | |
---|---|
Change from base Build 539a93a9-8db4-434e-944d-b98d10267b0b: | 0.06% |
Covered Lines: | 4931 |
Relevant Lines: | 5202 |
π - Coveralls
I am looking forward to using this feature <3
You beat me to it! I was doing literally the exact same thing today! Good stuff, hope this gets merged soon
@kamilmysliwiec any thoughts on PR? #2088 was closed due to fastify-multer being a too young library, but a year has passed since then
Any update on this pull? I think it should accepted @kamilmysliwiec
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?
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.
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?
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.
Any news on this?
Also wondering whatβs the status ?
Would also like to know what needs to happen to make this PR to be merged
@kamilmysliwiec any chance of this getting merged? Is it just a case to fix merge conflicts?
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 ?
I had to revert back a project from fastify to express because multer integration was too complex / would have taken too much time :(
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
For those who are stuck in this issues with mulher I found a way to workaround it fastify-multer and it worked quite fine.
any progress on this?
@kamilmysliwiec Any updates on this pull request?
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 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.
Because it relies on a tiny package that is out of our control.
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)
}