Introduction
Many developers despise dealing with file uploads. This can be attributed to a lack of knowledge about the best approach to take or difficulties determining how to configure their Nest.js application to handle file uploads. Many people may want to save their files directly to a MySQL database, or save image names and have the image saved on disk storage: it all depends on their preferences and the goals they want to achieve. This tutorial will teach you how to build a file uploading functionality using Nestjs and MySQL.
Prerequisites
Before you begin following this tutorial, ensure your system meets the following requirements:
- Your system is running Node.js with version 14 or later.
- Your system has a MySQL database installed.
- You have installed Postman.
Setting Up NestJS
Once the above-mentioned requirements are met, proceed to install the Nestjs CLI and create a new project by running the following commands:
$ npm i -g @nestjs/cli
$ nest new file-upload
These commands will install the Nestjs CLI and create a new Nestjs project with the folder structure below.
After the Nestjs project has been created, move on to the next step - install the required dependencies for your application by running the following command:
npm install --save @nestjs/typeorm typeorm mysql2
In the above command, you’ve installed the TypeORM and mysql2 modules: they will enable you to connect your application to a MySQL database and perform operations on it.
Setup the MySQL Database
With the above dependencies installed, proceed to set up and connect to your MySQL database. To get started, add the code in the app.module.ts
file with the code snippet below.
...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Image } from './image.entity';
@Module({
imports: [TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '1234',
database: 'blog',
entities: [Image],
synchronize: true,
}),
TypeOrmModule.forFeature([Image])
],
...
})
...
In the above code snippet, we imported TypeOrmModule
from the typeorm module we installed earlier. We used the forRoot
method to connect the application to a MySQL database and pass in the database credentials. Another thing to point out here is that entities
properties, which allowed us to specify the entities in our module and which will give us access to the Image
entity you’ll be creating shortly: we also have the synchronize
property set to true
to automatically migrate the database.
Create Image Entity
Next, let’s create the Image entity we mentioned earlier. To get started, create a image.entity.ts file in the src directory and add the code snippet below.
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity()
export class Image {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@CreateDateColumn()
dateCreated: Date;
@UpdateDateColumn()
dateUpdated: Date;
}
In the above code snippet, we imported the decorators we need to create an entity. Using these decorators we defined the properties of the entity. We have the id
field to generate random id’s for each record in the database using the @PrimaryGeneratedColumn()
decorator, the name
field to store the names of the images that will be uploaded using the @Column
decorator, the dateCreated and dateUpdate fields to save the date a record was created and updated using the @CreateDateColumn()
and @UpdateDateColumn()
.
Creating the Upload Service
With the Image entity created, let’s create a service to perform the CRUD operations to handle the file uploads. In the app.service.ts
file, add the code snippet below.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';
@Injectable()
export class AppService {
constructor(
@InjectRepository(Image)
private readonly imageRepository: Repository<Image>,
) {}
async getImages(): Promise<Image[]> {
return this.imageRepository.find();
}
async createImage(image: Image): Promise<Image> {
return this.imageRepository.save(image);
}
async getImage(id: number): Promise<Image> {
return this.imageRepository.findOneBy({ id });
}
async deleteImage(id: number): Promise<void> {
await this.imageRepository.delete(id);
}
}
In the above code snippet, we have imported the injectRepository
decorator to inject the imageRepository
into AppService
and the Repository
which provides you with the methods required to perform some operations on your database. So for the createImage
image service, we are saving the name of the image that is been uploaded which will be passed through the controller.
Creating the Upload Controller
Now let’s create the controllers to use the services. In the app.controller.ts
file and add the code snippet below.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';
@Injectable()
export class AppService {
constructor(
@InjectRepository(Image)
private readonly imageRepository: Repository<Image>,
) {}
async getImages(): Promise<Image[]> {
return this.imageRepository.find();
}
async createImage(image: Image): Promise<Image> {
return this.imageRepository.save(image);
}
async getImage(id: number): Promise<Image> {
return this.imageRepository.findOneBy({ id });
}
async deleteImage(id: number): Promise<void> {
await this.imageRepository.delete(id);
}
}
In the above code snippet, we imported a couple of decorators like FileInterceptor
, UploadedFile
, and UseInterceptors
. The FileInterceptor()
interceptor to the route handler extracts the file from the request using the @UploadedFile()
decorator. The FileInterceptor()
decorator is exported from the @nestjs/platform-express
package. The @UploadedFile()
decorator is exported from @nestjs/common
. The FileInterceptor()
decorator takes two arguments, fieldName
which is the string that supplies the name of the field from the HTML form that holds a file, and the options
which is an optional object of type MulterOptions. This is the same object used by the multer constructor.
Regarding the createImage
function, we have used the aforementioned decorators to handle the file upload using the FileInterceptor()
passing the field name for the image and we modified the FileInterceptor()
function to upload the image to disk by specifying the storage
property using the diskStorage
function available in multer
. Then we specified the location for the images and generated random names for the images. Also, we added a filter
property to restrict the upload of certain image formats. Now we get the file extracted using the @UploadedFile()
decorator and get the name and save it to the database. This way we can use the name of each image to get the image from the storage location.
For the above code to work, you need to install multer by running the command below in your terminal:
npm i -D @types/multer
Then, you need to register the multer module in the array of imports in the app.module.ts
file:
...
import { MulterModule } from '@nestjs/platform-express';
@Module({
...
MulterModule.register({
dest: './files',
}),],
...
The above configuration tells multer to handle the file upload and the location to upload the file to. Last but not least, we should create a files
folder in the src
directory to actually store the files.
Serving Files
To actually serve the images uploaded on your application to the user, you need to install the serve-static
module by running the command below.
npm install --save @nestjs/serve-static
Then, register the ServeStaticModule
in the array of imports in the app.module.ts
file with the code snippet below.
...
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
@Module({
...
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'files')
}),],
...
In the above code snippet, you’ve specified the location where the files are located and can be served from.
Testing the API
Now open Postman and test the application by sending a POST request to the endpoint localhost:4000/images
, and pass in the payload in the request body as a form-data.
If you now look at the files folder, you should see the file you have uploaded. Feel free to go ahead: test and play around with other routes as well.
Conclusion
Through this tutorial, you’ve learned how to handle file upload with NestJS and MySQL. You’ve learned how to connect to a MySQL database using TypeORM and you have also created an entity and uploaded images to the Nestjs application.
You've successfully built a file upload feature with NestJS and MySQL: for the future of your database and application, though, keep in mind that keeping a constant eye on your applications and databases consists of much more than building features for them: SQL clients like Arctype will let you write SQL queries and optimize them as well as visualize the data currently existing in your database, and the content existing on the Arctype blog will let you learn how to optimize all of your MySQL instances for security, availability, and performance at ease as well as provide you with lots of insight in regards to the database world as a whole.
For further reading, you can also read more about uploading files in Nestjs. For an extra challenge, try extending the application by protecting the delete and update routes. What will you build next?