Minio integration with nestjs | file upload & retrieve

Manuchehr
4 min readNov 29, 2024

--

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

My post is free for everyone, you can read it here.

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

Let me explain this quickly:

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

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=""

To obtain access and secret keys:

  • Copy & add access/secret keys to .env

Setup minio client in nestjs

Install npm minio package

pnpm add minio

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);
}
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 {}

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 {}

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);
}
},
);
});
}
}

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);
}
}

Send a file to POST /files/upload endpoint. It should return this response:

Let’s visit minio console and go main bucket to check if file was uploaded

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

Written by Manuchehr

Highly motivated Full-stack software engineer

No responses yet