001 Загрузка файлов
Очень хорошей практикой будет реализовать сервер, который примет изображения и будет сам на фронт отправлять их в формат webp
Тут мы так же сохраняем архитектуру классического сервера:
- контроллер хранит логику принятия и отправки результатов запросов посредством использования методов из сервиса
- сервис же выполняет конкретную логику для выполнения задачи
nest g module files
nest g controller files --no-spec
nest g service files --no-spec
Данный модуль позволит нам типизировать файлы
npm i -D @types/multer
Данный модуль позволит нам упростить работу с встроенным в ноду функционалом fs
(например, не писать проверки на существование папки)
npm i fs-extra
npm i -D @types/fs-extra
Данный модуль позволит нам вне зависимости от ОС определять корневую папку корректно
npm i app-root-path
npm i -D @types/app-root-path
Далее нам нужно будет реализовать функционал для генерации папки с именем дня, чтобы складывать в неё изображения
npm i date-fns
Первым делом нужно описать тот ответ, который мы должны отправить на клиент. Он будет содержать ссылку до изображения и его имя
src > files > dto > file-element.response.ts
export class FileElementResponse {
url: string;
name: string;
}
Interceptor - это декторатор в несте, который позволяет перехватить запрос и (например) воспользоваться напрямую функцией для работы с файлами
Конкретно можно обернуть контроллер в @UseInterceptors()
, в который вложим FileInterceptor('files')
('files'
- это имя мультиплатформы, в которой лежит файл), который уже и предоставит нам возможность работать с методами файлов
Метод uploadFiles
будет принимать в себя файлы-изображения и сохранять их на сервере
src > files > files.controller.ts
import { Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
import { HttpCode } from '@nestjs/common/decorators';
import { JwtAuthGuard } from '../auth/guards/jwt.guard';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileElementResponse } from './dto/file-element.response';
import { FilesService } from './files.service';
@Controller('files')
export class FilesController {
constructor(private readonly filesService: FilesService) {}
// загружаем файлы на сервер и сохраняем их
@Post('upload')
@HttpCode(200)
@UseGuards(JwtAuthGuard)
@UseInterceptors(FileInterceptor('files'))
async uploadFiles(@UploadedFile() file: Express.Multer.File): Promise<FileElementResponse[]> {
return this.filesService.saveFiles([file]);
}
}
Далее нам нужно реализовать сервис:
- генерируем текущую дату с помощью
format
- создаём строку папки, которая будет использоваться для генерации папки с изображениями
ensureDir
- данная функция обеспечивает создания дирректории (если папка есть, то хорошо, а если нет, то она будет создана)- далее нам нужно будет перебрать полученный массив файлов и записать их все на диск, а так же запушить все пути в массив
src > files > files.service.ts
import { Injectable } from '@nestjs/common';
import { FileElementResponse } from './dto/file-element.response';
import { format } from 'date-fns';
import { path } from 'app-root-path';
import { ensureDir, writeFile } from 'fs-extra';
@Injectable()
export class FilesService {
async saveFiles(files: Express.Multer.File[]): Promise<FileElementResponse[]> {
// генерируем текущую дату
const dateFolder = format(new Date(), 'yyyy-MM-dd');
// папка, куда будем загружать изображения
const uploadFolder = `${path}/uploads/${dateFolder}`;
// обеспечиваем создание папки
await ensureDir(uploadFolder);
// это тот ответ с ссылками, который нужно вернуть из метода
const res: FileElementResponse[] = [];
// проходимся по массиву полученных файлов
for (const file of files) {
// записываем файлы в нужную нам папку
await writeFile(`${uploadFolder}/${file.originalname}`, file.buffer);
// пушим ссылки до файлов в массив ответа из метода
res.push({
url: `/uploads/${dateFolder}/${file.originalname}`,
name: file.originalname,
});
}
return res;
}
}
Далее совершаем запрос с представленными параетрами:
И получаем в ответ массив ссылок на изображения:
Пометка
Данные строки в запросах должны быть равны при отправке файла
002 Конвертация изображений
Данная библиотечка очень оптимальная для использования в оверлоаде, так как она крайне быстро может оптимизировать и выдать нужное нам сконвертированное изображение
npm i sharp
npm i -D @types/sharp
Далее мы должны реализовать наш мини-тип, который поможет нам удобнее пользоваться типизацией файлов (внутренним экспрессовским и нашим классом).
Работает это потому, что наши используемые поля из обоих типов пересекаются
src > files > mfile.class.ts
export class MFile {
originalname: string;
buffer: Buffer;
constructor(file: Express.Multer.File | MFile) {
this.originalname = file.originalname;
this.buffer = file.buffer;
}
}
Далее добавляем метод convertToWebP
, который будет нам конвертировать изображения в WebP
src > files > files.service.ts
import { Injectable } from '@nestjs/common';
import { FileElementResponse } from './dto/file-element.response';
import { format } from 'date-fns';
import { path } from 'app-root-path';
import { ensureDir, writeFile } from 'fs-extra';
import * as sharp from 'sharp';
import { MFile } from './mfile.class';
@Injectable()
export class FilesService {
// меняем тип принимаемого файла
async saveFiles(files: MFile[]): Promise<FileElementResponse[]> {
const dateFolder = format(new Date(), 'yyyy-MM-dd');
const uploadFolder = `${path}/uploads/${dateFolder}`;
await ensureDir(uploadFolder);
const res: FileElementResponse[] = [];
for (const file of files) {
await writeFile(`${uploadFolder}/${file.originalname}`, file.buffer);
res.push({
url: `/uploads/${dateFolder}/${file.originalname}`,
name: file.originalname,
});
}
return res;
}
// функция конвертации
convertToWebP(file: Buffer): Promise<Buffer> {
return sharp(file).webp().toBuffer();
}
}
Далее нам нужно положить файлы в массив и проверить, приходит ли нам изображение или нет. Если пришло изображение, то добавляем его в массив с использованием нашей типизации через MFile
src > files > files.controller.ts
import {
Controller,
HttpCode,
Post,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { JwtAuthGuard } from 'src/auth/guards/jwt.guard';
import { FileElementResponse } from './dto/file-element.response';
import { FilesService } from './files.service';
import { MFile } from './mfile.class';
@Controller('files')
export class FilesController {
constructor(private readonly filesService: FilesService) {}
@Post('upload')
@HttpCode(200)
@UseGuards(JwtAuthGuard)
@UseInterceptors(FileInterceptor('files'))
async uploadFiles(@UploadedFile() file: Express.Multer.File): Promise<FileElementResponse[]> {
// сохраняем в контроллер массив изображений
const saveArray: MFile[] = [new MFile(file)];
// проверяем, является ли файл изображением
if (file.mimetype.includes('image')) {
const webp = await this.filesService.convertToWebP(file.buffer);
saveArray.push(
new MFile({
originalname: `${file.originalname.split('.')[0]}.webp`,
buffer: webp,
}),
);
}
// возвращаем ссылки на сохранённые файлы на клиент
return this.filesService.saveFiles(saveArray);
}
}
Так же у нас на сервере сохраняются изображения (ещё с прошлого урока) в двойном экземпляре
003 Serve файлов
Для сёрва наших файлов на клиент нам потребуется один модуль из неста:
npm i @nestjs/serve-static
Далее просто импортируем данный модуль и указываем корневую папку, из которой мы сможем получать нужные нам данные
src > files > files.module.ts
import { Module } from '@nestjs/common';
import { FilesController } from './files.controller';
import { FilesService } from './files.service';
import { ServeStaticModule } from '@nestjs/serve-static';
import { path } from 'app-root-path';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: `${path}/uploads`,
}),
],
controllers: [FilesController],
providers: [FilesService],
})
export class FilesModule {}
И по такому запросу (без api/upload
) мы можем получить нужные нам данные
Так же мы можем указать кастомный корень для получения наших изображений (например, если у нас несколько модулей для раздачи файлов)
src > files > files.module.ts
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: `${path}/uploads`,
serveRoot: '/static',
}),
],
controllers: [FilesController],
providers: [FilesService],
})
export class FilesModule {}
И теперь мы можем по нему получать данные с сервера