How to setup MinIO in a NestJS API to handle file uploads

Introduction

MinIO is an open source object storage server that is compatible with Amazon S3 cloud storage service. Using it in your NestJS API will allow you to store your files in it.

Requirements

Installation

Create a NestJS API

First, we need to create a NestJS API. To do so, we will use the NestJS CLI.

nest new <project-name>

Install the dependencies

To use MinIO in our NestJS API, we will use the MinIO client.

npm i minio && npm i -D @types/minio

We will also need to install the type definitions for Multer, which is a middleware for handling multipart/form-data.

npm i -D @types/multer

Install MinIO

To install MinIO, we will use Docker Compose.

touch docker-compose.yml

Fill your docker-compose.yml file with the following content:

version: '3.8'

services:
  minio:
    image: docker.io/bitnami/minio:2022
    environment:
      MINIO_ROOT_USER: minio
      MINIO_ROOT_PASSWORD: supersecret
    ports:
      - '9000:9000'
      - '9001:9001'
    volumes:
      - 'minio_data:/data'

volumes:
  minio_data:
    driver: local

Then, type the following command to start MinIO:

docker-compose up -d

You are now able to access the MinIO dashboard at http://127.0.0.1:9001.

Set up the NestJS Config module

To use MinIO in our NestJS API, we will use the NestJS Config module.

npm i @nestjs/config

Then, we will create a .env file in the root of our project.

touch .env

Fill your .env file with the following content, and make sure to replace the keys with your own :

MINIO_ENDPOINT='localhost'
MINIO_PORT=9000

MINIO_ACCESS_KEY=<my_access_key>
MINIO_SECRET_KEY=<my_secret_key>
MINIO_USE_SSL=false
MINIO_BUCKET_NAME=<my_bucket_name>

After doing that, we will import the ConfigModule in our app.module.ts file.

import { Module } from '@nestjs/common'

import { ConfigModule } from '@nestjs/config'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true
    })
  ]
})
export class AppModule {}

Usage

Create a MinIO service

To use MinIO in our NestJS API, we will create a MinIO service.

nest g service minio

Fill your minio.service.ts file with the following content:

import { Injectable } from '@nestjs/common'
import * as Minio from 'minio'

@Injectable()
class MinioService {
  private minioClient: Minio.Client
  private bucketName: string

  constructor(private readonly configService: ConfigService) {
    this.minioClient = new Minio.Client({
      endPoint: this.configService.get('MINIO_ENDPOINT'),
      port: Number(this.configService.get('MINIO_PORT')),
      useSSL: this.configService.get('MINIO_USE_SSL') === 'true',
      accessKey: this.configService.get('MINIO_ACCESS_KEY'),
      secretKey: this.configService.get('MINIO_SECRET_KEY')
    })
    this.bucketName = this.configService.get('MINIO_BUCKET_NAME')
  }

  async createBucketIfNotExists() {
    const bucketExists = await this.minioClient.bucketExists(this.bucketName)
    if (!bucketExists) {
      await this.minioClient.makeBucket(this.bucketName, 'eu-west-1')
    }
  }

  async uploadFile(file: Express.Multer.File) {
    const fileName = `${Date.now()}-${file.originalname}`
    await this.minioClient.putObject(
      this.bucketName,
      fileName,
      file.buffer,
      file.size
    )
    return fileName
  }

  async getFileUrl(fileName: string) {
    return await this.minioClient.presignedUrl('GET', this.bucketName, fileName)
  }

  async deleteFile(fileName: string) {
    await this.minioClient.removeObject(this.bucketName, fileName)
  }
}

Feel free to extend this service to suit your needs.

Example

To use our MinIO service, we will fill our app.controller.ts file, in order to handle file uploads and downloads of book covers.

// app.controller.ts
import {
  Controller,
  Get,
  Post,
  Param,
  Delete,
  UploadedFile,
  UseInterceptors
} from '@nestjs/common'

@Controller()
class AppController {
  constructor(private readonly minioService: MinioService) {}

  @Post('covers')
  @UseInterceptors(FileInterceptor('file'))
  async uploadBookCover(@UploadedFile() file: Express.Multer.File) {
    await this.minioService.createBucketIfNotExists()
    const fileName = await this.minioService.uploadFile(file)
    return fileName
  }

  @Get('covers/:fileName')
  async getBookCover(@Param('fileName') fileName: string) {
    const fileUrl = await this.minioService.getFileUrl(fileName)
    return fileUrl
  }

  @Delete('covers/:fileName')
  async deleteBookCover(@Param('fileName') fileName: string) {
    await this.minioService.deleteFile(fileName)
    return fileName
  }
}

Conclusion

You are now able to use MinIO in your NestJS API and store your files in it.

If you have any questions, feel free to reach me.

If you liked this article, follow me on Twitter @alexisbcz for more content like this.