DEV Community

Leonardo Minora
Leonardo Minora

Posted on

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

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)