Интеграция внешнего файлового сервера https://min.io в фулстек приложение на NestJS и Angular
В этой статье я подключу в проект внешний фа йловый сервер https://min.io и напишу дополнительные бэкенд и фронтенд модули для интеграции с ним.
1. Создаем Angular-библиотеку по работе с файлами
Создаем пустую Angular
-библиотеку для хранения компонент и сервисов для отправки файлов на сервер.
Команды
# Create Angular library
./node_modules/.bin/nx g @nx/angular:library --name=files-angular --buildable --publishable --directory=libs/core/files-angular --simpleName=true --strict=true --prefix= --standalone=true --selector= --changeDetection=OnPush --importPath=@nestjs-mod-fullstack/files-angular
# Change file with test options
rm -rf libs/core/files-angular/src/test-setup.ts
cp apps/client/src/test-setup.ts libs/core/files-angular/src/test-setup.ts
Вывод консоли
$ ./node_modules/.bin/nx g @nx/angular:library --name=files-angular --buildable --publishable --directory=libs/core/files-angular --simpleName=true --strict=true --prefix= --standalone=true --selector= --changeDetection=OnPush --importPath=@nestjs-mod-fullstack/files-angular
NX Generating @nx/angular:library
CREATE libs/core/files-angular/project.json
CREATE libs/core/files-angular/README.md
CREATE libs/core/files-angular/ng-package.json
CREATE libs/core/files-angular/package.json
CREATE libs/core/files-angular/tsconfig.json
CREATE libs/core/files-angular/tsconfig.lib.json
CREATE libs/core/files-angular/tsconfig.lib.prod.json
CREATE libs/core/files-angular/src/index.ts
CREATE libs/core/files-angular/jest.config.ts
CREATE libs/core/files-angular/src/test-setup.ts
CREATE libs/core/files-angular/tsconfig.spec.json
CREATE libs/core/files-angular/src/lib/files-angular/files-angular.component.css
CREATE libs/core/files-angular/src/lib/files-angular/files-angular.component.html
CREATE libs/core/files-angular/src/lib/files-angular/files-angular.component.spec.ts
CREATE libs/core/files-angular/src/lib/files-angular/files-angular.component.ts
CREATE libs/core/files-angular/.eslintrc.json
UPDATE tsconfig.base.json
NX 👀 View Details of files-angular
Run "nx show project files-angular" to view details about this project.
2. Создаем NestJS-библиотеку по работе с файлами
Создаем пустую NestJS
-библиотеку.
Команды
./node_modules/.bin/nx g @nestjs-mod/schematics:library files --buildable --publishable --directory=libs/core/files --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
Вывод консоли
$ ./node_modules/.bin/nx g @nestjs-mod/schematics:library files --buildable --publishable --directory=libs/core/files --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
NX Generating @nestjs-mod/schematics:library
CREATE libs/core/files/tsconfig.json
CREATE libs/core/files/src/index.ts
CREATE libs/core/files/tsconfig.lib.json
CREATE libs/core/files/README.md
CREATE libs/core/files/package.json
CREATE libs/core/files/project.json
CREATE libs/core/files/.eslintrc.json
CREATE libs/core/files/jest.config.ts
CREATE libs/core/files/tsconfig.spec.json
UPDATE tsconfig.base.json
CREATE libs/core/files/src/lib/files.configuration.ts
CREATE libs/core/files/src/lib/files.constants.ts
CREATE libs/core/files/src/lib/files.environments.ts
CREATE libs/core/files/src/lib/files.module.ts
3. Устанавливаем дополнительные библиотеки
Устанавливаем JS
-клиент и NestJS
-модуль для работы с файловым сервером minio
с фронтенда и бэкенда.
Команды
npm install --save minio nestjs-minio @nestjs-mod/minio
Вывод консоли
$ npm install --save minio nestjs-minio @nestjs-mod/minio
added 29 packages, removed 2 packages, and audited 2916 packages in 22s
362 packages are looking for funding
run `npm fund` for details
41 vulnerabilities (19 low, 7 moderate, 15 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues possible (including breaking changes), run:
npm audit fix --force
Some issues need review, and may require choosing
a different dependency.
Run `npm audit` for details.
4. Подключаем новые модули в бэкенд
apps/server/src/main.ts
import {
DOCKER_COMPOSE_FILE,
DockerCompose,
DockerComposeAuthorizer,
DockerComposeMinio,
DockerComposePostgreSQL,
} from '@nestjs-mod/docker-compose';
// ...
import { MinioModule } from '@nestjs-mod/minio';
// ...
import { ExecutionContext } from '@nestjs/common';
// ...
bootstrapNestApplication({
modules: {
// ...
core: [
MinioModule.forRoot(),
],
infrastructure: [
DockerComposeMinio.forRoot({
staticConfiguration: { image: 'bitnami/minio:2024.11.7' },
}),
]}
);
5. Запускаем генерацию дополнительного кода по инфраструктуре
Команды
npm run docs:infrastructure
6. Добавляем весь необходимый код в модуль FilesModule (NestJS-библиотека)
Так как основная логика по подключению к файловому серверу и работа с ним происходит с помощью библиотеки @nestjs-mod/minio
, то в нашей новой библиотеке будет только контроллер который будет предоставлять фронтенд приложению необходимые методы и при этом проверять права пользователя.
Загрузка файла с фронтенда происходит с помощью временной ссылки напрямую в файловый сервер, временную ссылку создает наш бэкенд.
Удалять загруженные файлы могут только пользователи кто загрузил файл и администраторы сайта.
Идентификатор пользователя должен находится в Request
, в поле externalUserId
.
Создаем файл libs/core/files/src/lib/controllers/files.controller.ts
import { Controller, Get, Post, Query } from '@nestjs/common';
import { MinioConfiguration, MinioFilesService, PresignedUrlsRequest, PresignedUrls as PresignedUrlsResponse } from '@nestjs-mod/minio';
import { ApiExtraModels, ApiOkResponse, ApiProperty } from '@nestjs/swagger';
import { FilesError, FilesErrorEnum } from '../files.errors';
import { CurrentFilesRequest } from '../files.decorators';
import { FilesRequest } from '../types/files-request';
import { StatusResponse } from '@nestjs-mod-fullstack/common';
import { map } from 'rxjs';
import { FilesRole } from '../types/files-role';
export class GetPresignedUrlArgs implements PresignedUrlsRequest {
@ApiProperty({ type: String })
ext!: string;
}
export class PresignedUrls implements PresignedUrlsResponse {
@ApiProperty({ type: String })
downloadUrl!: string;
@ApiProperty({ type: String })
uploadUrl!: string;
}
export class DeleteFileArgs {
@ApiProperty({ type: String })
downloadUrl!: string;
}
@ApiExtraModels(FilesError)
@Controller()
export class FilesController {
constructor(private readonly minioConfiguration: MinioConfiguration, private readonly minioFilesService: MinioFilesService) {}
@Get('/files/get-presigned-url')
@ApiOkResponse({ type: PresignedUrls })
getPresignedUrl(@Query() getPresignedUrlArgs: GetPresignedUrlArgs, @CurrentFilesRequest() filesRequest: FilesRequest) {
const bucketName = Object.entries(this.minioConfiguration.buckets || {})
.filter(([, options]) => options.ext.includes(getPresignedUrlArgs.ext))
.map(([name]) => name)?.[0];
if (!bucketName) {
throw new FilesError(`Uploading files with extension "{{ext}}" is not supported`, FilesErrorEnum.FORBIDDEN, { ext: getPresignedUrlArgs.ext });
}
return this.minioFilesService.getPresignedUrls({
bucketName,
expiry: 60,
ext: getPresignedUrlArgs.ext,
userId: filesRequest.externalUserId,
});
}
@Post('/files/delete-file')
@ApiOkResponse({ type: StatusResponse })
deleteFile(@Query() deleteFileArgs: DeleteFileArgs, @CurrentFilesRequest() filesRequest: FilesRequest) {
if (filesRequest.filesUser?.userRole === FilesRole.Admin || deleteFileArgs.downloadUrl.includes(`/${filesRequest.externalUserId}/`)) {
return this.minioFilesService.deleteFile(deleteFileArgs.downloadUrl).pipe(map(() => ({ message: 'ok' })));
}
throw new FilesError(`Only those who uploaded files can delete them`, FilesErrorEnum.FORBIDDEN);
}
}
Добавляем контроллер в FilesModule
, и подключаем MinioModule.forFeature
для доступа к сервисам внешнего модуля.
Обновляем файл libs/core/files/src/lib/files.module.ts
import { createNestModule, NestModuleCategory } from '@nestjs-mod/common';
import { MinioModule } from '@nestjs-mod/minio';
import { FilesController } from './controllers/files.controller';
import { FILES_FEATURE, FILES_MODULE } from './files.constants';
export const { FilesModule } = createNestModule({
moduleName: FILES_MODULE,
moduleCategory: NestModuleCategory.feature,
controllers: [FilesController],
imports: [
MinioModule.forFeature({
featureModuleName: FILES_FEATURE,
}),
],
});