NestJS - criar um endpoint para upload de diversos arquivos

leonardominora

Leonardo Minora

Posted on September 15, 2024

NestJS - criar um endpoint para upload de diversos arquivos

Informações gerais

objetivo

  • criar 1 endpoint para API de upload de múltiplos arquivos

notas de aula

sumário

  1. pegar o código do projeto anterior
  2. executar a API
  3. criar e configurar 1 endpoint para receber vários arquivos
  4. criar método no serviço para retornar as informações dos arquivos

para receber vários arquivos, o nestjs disponibiliza os seguintes interceptors:

  • FilesInterceptor - identifica um array de arquivos com o mesmo nome de campo do formulário;
  • FileFieldsInterceptor -
  • AnyFilesInterceptor -

1. pegar o código do projeto anterior

a nota de aula anterior é NestJS - Upload de 1 arquivo que criou um projeto javascript para uma API Rest usando nestjs e typescript.

pode utilizar o seu próprio código, ou baixar o zip ou fazer o clone do repositório github.

2. Executar a API

lembre de acessar a pasta do projeto!!!

antes de executar a api, lembrar de instalar as bibliotecas do projeto com o comando npm i.

lançar a api em modo desenvolvimento executando o comando npm com a opção run para executar o script start:dev.

[upload-api] $ npm run start:dev

Enter fullscreen mode Exit fullscreen mode

a execução deverá produzir um resultado parecido com o console abaixo.

[11:58:35] Starting compilation in watch mode...

[11:58:37] Found 0 errors. Watching for file changes.

[Nest] 12346  - 15/09/2024, 11:58:38     LOG [NestFactory] Starting Nest application...
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [InstanceLoader] AppModule dependencies initialized +14ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [InstanceLoader] UploadModule dependencies initialized +1ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RoutesResolver] AppController {/}: +15ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RoutesResolver] UploadController {/upload}: +0ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RouterExplorer] Mapped {/upload/exemplo-simples, POST} route +2ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [NestApplication] Nest application successfully started +4ms

Enter fullscreen mode Exit fullscreen mode

3. criar e configurar 1 endpoint para receber vários arquivos

adicionar um endpoint no controller src/upload/upload.controller.ts, atualizando as importações, conforme código (diff) abaixo.

import {
  Controller,
  Post,
  UploadedFile,
++  UploadedFiles,
  UseInterceptors,
} from '@nestjs/common';
--import { FileInterceptor } from '@nestjs/platform-express';
++import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import {
++  ApiBadRequestResponse,
  ApiBody,
  ApiConsumes,
  ApiOperation,
  ApiResponse,
  ApiTags,
} from '@nestjs/swagger';
import { UploadService } from './upload.service';

@Controller('upload')
@ApiTags('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  @Post('exemplo-simples')
  @UseInterceptors(FileInterceptor('arquivo'))
  @ApiConsumes('multipart/form-data')
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        arquivo: {
          type: 'string',
          format: 'binary',
        },
      },
    },
  })
  @ApiOperation({ summary: 'Exemplo de upload de 1 arquivo qualquer' })
  @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' })
  @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' })
  uploadArquivoSimples(@UploadedFile() arq: Express.Multer.File) {
    console.log(arq);

    return this.uploadService.responderInformacaoArquivo(arq);
  }

++  @Post('arquivos')
++  @UseInterceptors(FilesInterceptor('arquivos'))
++  @ApiConsumes('multipart/form-data')
++  @ApiBody({
++    schema: {
++      type: 'object',
++      properties: {
++        arquivos: {
++          type: 'array',
++          items: {
++            type: 'string',
++            format: 'binary',
++          },
++        },
++      },
++    },
++  })
++  @ApiResponse({
++    status: 201,
++    description: 'Arquivo(s) enviado(s) com sucesso.',
++  })
++  @ApiBadRequestResponse({
++    status: 400,
++    description: 'Erro no envio de arquivos.',
++  })
++  uploadArquivos(@UploadedFiles() arquivos: Array<Express.Multer.File>) {
++    return {
++      estado: 'ok',
++      data: {
++        quantidade: arquivos?.length,
++      },
++    };
++  }
}

Enter fullscreen mode Exit fullscreen mode

foi adicionado nas importações o interceptor FilesInterceptor e o decorator UploadedFiles responsáveis por interceptar e extrair vários arquivos em uma array nomeada como arquivos no formulário.

o decorator @Post configura o verdo POST e o path arquivos do endpoint.
enquanto o decorator @UseInterceptors configura o uso do FilesInterceptor para interceptar a requisitação HTTTP(S) a procura da variável arquivos dentro do formulário no corpo da mensagem.

as próximas linhas contém os decorators @Api (ApiConsumes, ApiBody, ApiResponse, e ApiBadRequestResponse) para configurar a documentação do swagger.

a próxima linha, uploadArquivos(@UploadedFiles() arquivos: Array<Express.Multer.File>) especifica o método uploadArquivos.
este método tem como parâmetro a variável arquivos que irá receber um array contendo itens cada tipados com Express.Multer.File.
o parâmetro arquivos também esta decorado com UploadedFiles que é responsável por extrair o array de arquivos da mensagem HTTP.

ao salvar, a api será relançada e o terminal onde ela esta sendo executada deverá ficar como o console abaixo.

[11:58:35] Starting compilation in watch mode...

[11:58:37] Found 0 errors. Watching for file changes.

[Nest] 12346  - 15/09/2024, 11:58:38     LOG [NestFactory] Starting Nest application...
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [InstanceLoader] AppModule dependencies initialized +14ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [InstanceLoader] UploadModule dependencies initialized +1ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RoutesResolver] AppController {/}: +15ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RoutesResolver] UploadController {/upload}: +0ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [RouterExplorer] Mapped {/upload/exemplo-simples, POST} route +2ms
[Nest] 12346  - 15/09/2024, 11:58:38     LOG [NestApplication] Nest application successfully started +4ms

Enter fullscreen mode Exit fullscreen mode

para testar, use a documentação da api acessando o endereço http://localhost:3000/docs/
clique no botão Try it out, escolher alguns arquivos, e clicar em Execute.
a operação deve estar semelhante com a figura abaixo e o resultado após a execução com o json abaixo da figura.

Image description

{
  "estado": "ok",
  "data": {
    "quantidade": 3
  }
}

Enter fullscreen mode Exit fullscreen mode

4. criar método no serviço para retornar as informações dos arquivos

no arquivo src/upload/upload.service.ts crie o método responderInformacoesArquivos(arquivos: Array<Express.Multer.File>) e coloque a implementação conforme arquivo (diff) abaixo.

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

@Injectable()
export class UploadService {
  responderInformacaoArquivo(arquivo: Express.Multer.File) {
    return {
      estado: 'ok',
      dados: {
        nome: arquivo.originalname,
        tamanho: arquivo.size,
        mimetype: arquivo.mimetype,
        encode: arquivo.encoding,
      },
    };
  }
++
++  responderInformacoesArquivos(arquivos: Array<Express.Multer.File>) {
++    const informacoes = arquivos.map((arquivo) => {
++      return {
++        nome: arquivo.originalname,
++        tamanho: arquivo.size,
++        mimetype: arquivo.mimetype,
++        encode: arquivo.encoding,
++      };
++    });
++    return {
++      estado: 'ok',
++      dados: {
++        quantidade: arquivos?.length,
++        arquivos: informacoes,
++      },
++    };
++  }
}
Enter fullscreen mode Exit fullscreen mode

após a modificação do service terá de modificar o controller no arquivo src/upload/upload.controller.ts conforme arquivo abaixo para chamar o método responderInformacoesArquivos.

import {
  Controller,
  Post,
  UploadedFile,
  UploadedFiles,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import {
  ApiBadRequestResponse,
  ApiBody,
  ApiConsumes,
  ApiOperation,
  ApiResponse,
  ApiTags,
} from '@nestjs/swagger';
import { UploadService } from './upload.service';

@Controller('upload')
@ApiTags('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  @Post('exemplo-simples')
  @UseInterceptors(FileInterceptor('arquivo'))
  @ApiConsumes('multipart/form-data')
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        arquivo: {
          type: 'string',
          format: 'binary',
        },
      },
    },
  })
  @ApiOperation({ summary: 'Exemplo de upload de 1 arquivo qualquer' })
  @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' })
  @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' })
  uploadArquivoSimples(@UploadedFile() arq: Express.Multer.File) {
    console.log(arq);

    return this.uploadService.responderInformacaoArquivo(arq);
  }

  @Post('arquivos')
  @ApiConsumes('multipart/form-data')
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        arquivos: {
          type: 'array',
          items: {
            type: 'string',
            format: 'binary',
          },
        },
      },
    },
  })
  @ApiResponse({
    status: 201,
    description: 'Arquivo(s) enviado(s) com sucesso.',
  })
  @ApiBadRequestResponse({
    status: 400,
    description: 'Erro no envio de arquivos.',
  })
  @UseInterceptors(FilesInterceptor('arquivos'))
  uploadArquivos(@UploadedFiles() arquivos: Array<Express.Multer.File>) {
--    return {
--      estado: 'ok',
--      dados: {
--        quantidade: arquivos?.length,
--        arquivos: informacoes,
--      },
--    };
++    return this.uploadService.responderInformacoesArquivos(arquivos);
  }
}

Enter fullscreen mode Exit fullscreen mode

para testar, pode usar novamente a documentação da API conforme a figura anterior.
o resultado agora será parecido conforme o json abaixo.

{
  "estado": "ok",
  "dados": {
    "quantidade": 3,
    "arquivos": [
      {
        "nome": "README.md",
        "tamanho": 1373,
        "mimetype": "text/markdown",
        "encode": "7bit"
      },
      {
        "nome": "package.json",
        "tamanho": 2020,
        "mimetype": "application/json",
        "encode": "7bit"
      },
      {
        "nome": "Captura de tela de 2024-09-15 15-35-33.png",
        "tamanho": 44726,
        "mimetype": "image/png",
        "encode": "7bit"
      }
    ]
  }
}

Enter fullscreen mode Exit fullscreen mode

Referência e link

💖 💪 🙅 🚩
leonardominora
Leonardo Minora

Posted on September 15, 2024

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

Sign up to receive the latest update from our blog.

Related