Uploading Files to a NestJS Backend
In a recent tutorial, we covered how to upload files from a frontend application using <input type="file">
to a simple Node/Express server. In this tutorial, we will be walking through how to use NestJS as the backend server instead (NestJS actually sits on top of Node/Express).
I would recommend reading the previous tutorial for a lot more context on how uploading files to a server work. The previous tutorial covers what multipart/form-data
is and how it relates to the forms we need to create to upload files, as well as using the FormData
API, the role of multer
and busboy
and more.
You can also watch the video version of this tutorial below:
This tutorial will just be focusing on the basics of implementing the backend with NestJS. For reference, I will paste the code from the previous tutorial that we will be using to send files to our NestJS server below. The key parts are:
Listening for a the file change event:
<input type="file" onChange={(ev) => this.onFileChange(ev)}></input>
Storing a reference to the file for use later:
onFileChange(fileChangeEvent) {
this.file = fileChangeEvent.target.files[0];
}
Constructing the FormData
that will be sent with POST
request to the NestJS server:
let formData = new FormData();
formData.append('photo', this.file, this.file.name);
The example frontend code below was created with StencilJS, but you will find example for Angular and React in the previous tutorial if you wish. The concept is more or less the same regardless, so if you are comfortable with your chosen framework it should be relatively straightforward to translate these concepts into your framework.
Outline
Source codeFrontend Code for Single File Upload
import { Component, h } from '@stencil/core';
@Component({
tag: 'app-home',
styleUrl: 'app-home.css',
})
export class AppHome {
private file: File;
onFileChange(fileChangeEvent) {
this.file = fileChangeEvent.target.files[0];
}
async submitForm() {
let formData = new FormData();
formData.append('photo', this.file, this.file.name);
try {
const response = await fetch('http://localhost:3000/photos/upload', {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(response.statusText);
}
console.log(response);
} catch (err) {
console.log(err);
}
}
render() {
return [
<ion-header>
<ion-toolbar color="primary">
<ion-title>Home</ion-title>
</ion-toolbar>
</ion-header>,
<ion-content class="ion-padding">
<ion-item>
<ion-label>Image</ion-label>
<input type="file" onChange={ev => this.onFileChange(ev)}></input>
</ion-item>
<ion-button color="primary" expand="full" onClick={() => this.submitForm()}>
Upload Single
</ion-button>
</ion-content>,
];
}
}
Frontend Code for Multiple File Upload
import { Component, h } from '@stencil/core';
@Component({
tag: 'app-home',
styleUrl: 'app-home.css',
})
export class AppHome {
private fileOne: File;
private fileTwo: File;
private fileThree: File;
onFileOneChange(fileChangeEvent) {
this.fileOne = fileChangeEvent.target.files[0];
}
onFileTwoChange(fileChangeEvent) {
this.fileTwo = fileChangeEvent.target.files[0];
}
onFileThreeChange(fileChangeEvent) {
this.fileThree = fileChangeEvent.target.files[0];
}
async submitMultipleForm() {
let formData = new FormData();
formData.append('photos[]', this.fileOne, this.fileOne.name);
formData.append('photos[]', this.fileTwo, this.fileTwo.name);
formData.append('photos[]', this.fileThree, this.fileThree.name);
try {
const response = await fetch('http://localhost:3000/photos/uploads', {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(response.statusText);
}
console.log(response);
} catch (err) {
console.log(err);
}
}
render() {
return [
<ion-header>
<ion-toolbar color="primary">
<ion-title>Home</ion-title>
</ion-toolbar>
</ion-header>,
<ion-content class="ion-padding">
<input type="file" onChange={ev => this.onFileOneChange(ev)}></input>
<input type="file" onChange={ev => this.onFileTwoChange(ev)}></input>
<input type="file" onChange={ev => this.onFileThreeChange(ev)}></input>
<ion-button color="primary" expand="full" onClick={() => this.submitMultipleForm()}>
Upload Multiple
</ion-button>
</ion-content>,
];
}
}
1. Create the NestJS Project
We will start from scratch by creating a new NestJS project with the Nest CLI:
nest new
Although this is not strictly required for this example (you could just create an upload
route in your applications main module) we will follow best practices and organise this application in modules. We will create a photos
module using the nest g module
command:
nest g module photos
We will also use the Nest CLI to generate a controller
for us:
nest g controller photos
We will also call the enableCors
method in the main.ts file so that our local frontend application can interact with the server:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
await app.listen(3000);
}
bootstrap();
2. Create the Routes
We will be creating two routes for this server: one to handle a single file upload and one to handle multiple file uploads.
import { Controller, Post } from '@nestjs/common';
@Controller('photos')
export class PhotosController {
@Post('upload')
uploadSingle() {}
@Post('uploads')
uploadMultiple() {}
}
Typically in a NestJS project we might define a DTO that would define the data that is being sent through a POST
request, and then inject that DTO into the method for a particular route. Although you can POST
other data along with your file(s) that will be available through the DTO, the files themselves are handled differently by NestJS.
3. Add File Interceptors
NestJS has built in functionality to intercept any files being uploaded and use multer
to handle what to do with them. We can use FileInterceptor
to intercept a file and then the @UploadedFile
decorator to get a reference to the file that is being uploaded inside of our route handler method. In the case of multiple files being uploaded we can use FilesInterceptor
and @UploadedFiles
:
import {
Controller,
Post,
UseInterceptors,
UploadedFile,
UploadedFiles,
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
@Controller('photos')
export class PhotosController {
@Post('upload')
@UseInterceptors(FileInterceptor('photo', { dest: './uploads' }))
uploadSingle(@UploadedFile() file) {
console.log(file);
}
@Post('uploads')
@UseInterceptors(FilesInterceptor('photos[]', 10, { dest: './uploads' }))
uploadMultiple(@UploadedFiles() files) {
console.log(files);
}
}
All we need to do is specify the name of the field that contains the file(s) inside of File(s)Interceptor
and then any multer
options we want to use. As we did with the simple Node/Express example, we are just using a simple multer
configuration:
{
dest: './uploads';
}
which will automatically save any uploaded files to the uploads
directory. If this directory doesn't already exist, it will automatically be created when you start your NestJS server.
The second parameter in the FilesInterceptor
for multiple file uploads is a maxCount
configuration, which in this case means no more then 10
files will be able to be uploaded at a time. Although we are just using a single multer
configuration here by specifying the dest
you can add any further multer options you want inside of this object.
Thanks for reading!
You can find the source code for the NestJS server, as well as the simple Node/Express server and the frontend code for StencilJS, Angular, and React below.