Minio integration with nestjs | file upload & retrieve

manuchehr

Manuchehr

Posted on November 29, 2024

Minio integration with nestjs | file upload & retrieve

What is minio? Minio is *free, open-source, scalable S3 compatible object storage.

First of all, let's setup minio in docker container. I use bitnami's minio package. Add minio service to your docker-compose.yaml:

services:
  minio:
    image: bitnami/minio:2024.11.7
    restart: always
    ports:
      - ${MINIO_PORT}:${MINIO_PORT}
      - ${MINIO_CONSOLE_PORT}:${MINIO_CONSOLE_PORT}
    environment:
      MINIO_ROOT_USER: ${MINIO_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD}
      MINIO_DEFAULT_BUCKETS: ${MINIO_BUCKET}
      MINIO_API_PORT_NUMBER: ${MINIO_PORT}
      MINIO_CONSOLE_PORT_NUMBER: ${MINIO_CONSOLE_PORT}
    volumes:
      - minio-data:/bitnami/minio/data

volumes:
  minio-data:
    driver: local

Enter fullscreen mode Exit fullscreen mode

Let me explain this quickly:

  1. ports: MINIO_PORT is minio's port for API requests, while as the name says MINIO_CONSOLE_PORT is minio's dashboard port:
    Minio console

  2. MINIO_ROOT_USER & MINIO_ROOT_PASSWORD are for login credentials to login minio console dashboard.

For more information about env variables of minio docker image, visit bitnami's minion docker image

Add these variables to your .env file:

MINIO_PORT=9000
MINIO_CONSOLE_PORT=8000
MINIO_USER="admin"
MINIO_PASSWORD="veryhardpassword"
MINIO_BUCKET="main"
MINIO_ENDPOINT="localhost"
MINIO_ACCESS_KEY=""
MINIO_SECRET_KEY=""
Enter fullscreen mode Exit fullscreen mode

To obtain access and secret keys:

  1. Go to minio console (it's http://localhost:8000 in our example) & sign in.

  2. Go Access Keys & Create access key
    Minio console access keys
    Minio console create access key

  3. Copy & add access/secret keys to .env

Setup minio client in nestjs

Install npm minio package

pnpm add minio
Enter fullscreen mode Exit fullscreen mode

Create minio/minio.decorator.ts & minio/minio.module.ts

import { Inject } from '@nestjs/common';

export const MINIO_TOKEN = 'MINIO_INJECT_TOKEN';

export function InjectMinio(): ParameterDecorator {
  return Inject(MINIO_TOKEN);
}
Enter fullscreen mode Exit fullscreen mode
import { Global, Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MINIO_TOKEN } from './minio.decorator';
import * as Minio from 'minio';

@Global()
@Module({
  exports: [MINIO_TOKEN],
  providers: [
    {
      inject: [ConfigService],
      provide: MINIO_TOKEN,
      useFactory: async (
        configService: ConfigService,
      ): Promise<Minio.Client> => {
        const client = new Minio.Client({
          endPoint: configService.getOrThrow("MINIO_ENDPOINT"),
          port: +configService.getOrThrow("MINIO_PORT"),
          accessKey: configService.getOrThrow("MINIO_ACCESS_KEY"),
          secretKey: configService.getOrThrow("MINIO_SECRET_KEY"),
          useSSL: false,
        });
        return client;
      },
    },
  ],
})
export class MinioModule {}
Enter fullscreen mode Exit fullscreen mode

If you want to use minio in only one module (files module for example) you can remove @Global() decorator.

Import it to app.module.ts:

@Module({
  imports: [
    ...
    MinioModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Now let's test it out. Inject minio to your server (files.service.ts for example)

import { Injectable } from '@nestjs/common';
import { randomUUID } from 'crypto';
import * as Minio from 'minio';
import { InjectMinio } from 'src/minio/minio.decorator';

@Injectable()
export class FilesService {
  protected _bucketName = 'main';

  constructor(@InjectMinio() private readonly minioService: Minio.Client) {}

  async bucketsList() {
    return await this.minioService.listBuckets();
  }

  async getFile(filename: string) {
    return await this.minioService.presignedUrl(
      'GET',
      this._bucketName,
      filename,
    );
  }

  uploadFile(file: Express.Multer.File) {
    return new Promise((resolve, reject) => {
      const filename = `${randomUUID().toString()}-${file.originalname}`;
      this.minioService.putObject(
        this._bucketName,
        filename,
        file.buffer,
        file.size,
        (error, objInfo) => {
          if (error) {
            reject(error);
          } else {
            resolve(objInfo);
          }
        },
      );
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

and it's my files.controller.ts

import { Body, Controller, Get, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FilesService } from './files.service';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('files')
export class FilesController {
  constructor(readonly service: FilesService) {}

  @Get('buckets')
  bucketsList() {
    return this.service.bucketsList();
  }

  @Get('file-url/:name')
  getFile(@Param('name') name: string) {
    return this.service.getFile(name);
  }

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(
    @UploadedFile('file') file: Express.Multer.File,
  ) {
    payload.file = file;
    return this.service.uploadFile(file);
  }
}
Enter fullscreen mode Exit fullscreen mode

Send a file to POST /files/upload endpoint. It should return this response:
Minio integration with nestjs | file upload & retrieve

Let's visit minio console and go main bucket to check if file was uploaded
Minio integration with nestjs | file upload & retrieve
It's uploaded 🥳

Now let's test generating file URL for uploaded file (retrieve file) GET /files/file-url/<file_name>. It should return URL to retrieve file something like this:
http://localhost:8000/main/2254a964-28dd-4c35-8494-5e767693cd26-iOS%2BSyllabus.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=m0iWmenUgJi2xBcFNNPF%2F20241129%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20241129T065735Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a052b98dcfa95a4d1cdb1c9b0b91d1263440adf184aab5238e6d572c0f42e7c5
If you visit this URL you can retrieve that file.

You can find all minio methods with this link


*free - MinIO is free for self-hosted use under the GNU AGPLv3 license, which allows you to run and modify the software as long as any modifications you make are also made available under the same license. This license ensures freedom to use, study, share, and modify the software.

That's all. Thanks for reading I hope it's beneficial for you. If you find any mistake or issue in this post, feel free to comment or let me know.

💖 💪 🙅 🚩
manuchehr
Manuchehr

Posted on November 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related