DEV Community

Leonardo Minora
Leonardo Minora

Posted on • Edited on

NestJS - Armazenamento local de upload

Informações gerais

objetivo

  • criar 1 endpoint de upload de 1 arquivo com armazenamento em disco local
  • criar 1 endpoint de download de 1 arquivo que foi armazenado em disco local

notas de aula

sumário

  1. pegar o código base
  2. acessar pasta do projeto e instalar bibliotecas do projeto
  3. executar a api
  4. incluir módulo nestjs para os novos endpoints
  5. codar o upload de arquivo com armazenamento local
  6. codar o download de arquivo com armazenamento local
  7. codar para adicionar exceção para arquivo não encontrado

1. pegar o código base

pode utilizar o seu próprio código, ou baixar o zip ou fazer o clone do repositório github com o código-fonte do projeto da nota de aula anterior.

lembrando que fazendo o clone do repositório github, precisará executar na pasta do projeto o comando git checkout -b 02-upload-arquivos-multiplos origin/02-upload-arquivos-multiplos.

2. acessar pasta do projeto e instalar bibliotecas do projeto

[upload-api] $ npm install 
Enter fullscreen mode Exit fullscreen mode

3. executar a api

[upload-api] $ npm run start:dev 
Enter fullscreen mode Exit fullscreen mode

4. incluir módulo nestjs para os novos endpoints

[upload-api] $ npx @nestjs/cli generate resource armazenamento --no-spec 
Enter fullscreen mode Exit fullscreen mode
[15:08:33] Starting compilation in watch mode... [15:08:35] Found 0 errors. Watching for file changes. [Nest] 134297 - 17/09/2024, 15:08:35 LOG [NestFactory] Starting Nest application... [Nest] 134297 - 17/09/2024, 15:08:35 LOG [InstanceLoader] AppModule dependencies initialized +21ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [InstanceLoader] ArmazenamentoModule dependencies initialized +0ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [InstanceLoader] UploadModule dependencies initialized +0ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [RoutesResolver] AppController {/}: +18ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [RouterExplorer] Mapped {/, GET} route +2ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [RoutesResolver] UploadController {/upload}: +0ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [RouterExplorer] Mapped {/upload/exemplo-simples, POST} route +1ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [RouterExplorer] Mapped {/upload/arquivos, POST} route +0ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [RoutesResolver] ArmazenamentoController {/armazenamento}: +1ms [Nest] 134297 - 17/09/2024, 15:08:35 LOG [NestApplication] Nest application successfully started +2ms 
Enter fullscreen mode Exit fullscreen mode

5. codar o upload de arquivo com armazenamento local

objetivo: criar o endpoint para upload de 1 arquivo com documentação swagger.

modificar o arquivo src/armazenamento/armazenamento.controller.ts

--import { Controller } from '@nestjs/common'; ++import { ++ Controller, ++ Post, ++ UploadedFile, ++ UseInterceptors, ++} from '@nestjs/common'; ++import { ArmazenamentoService } from './armazenamento.service'; ++import { ++ ApiBody, ++ ApiConsumes, ++ ApiOperation, ++ ApiResponse, ++ ApiTags, ++} from '@nestjs/swagger'; ++import { FileInterceptor } from '@nestjs/platform-express'; ++ ++@Controller('armazenamento') ++@ApiTags('armazenamento') export class ArmazenamentoController {  constructor(private readonly armazenamentoService: ArmazenamentoService) {} ++ ++ @Post() ++ @UseInterceptors(FileInterceptor('imagem')) ++ @ApiConsumes('multipart/form-data') ++ @ApiBody({ ++ schema: { ++ type: 'object', ++ properties: { ++ imagem: { ++ type: 'string', ++ format: 'binary', ++ }, ++ }, ++ }, ++ }) ++ @ApiOperation({ summary: 'Upload de arquivo com armazenamento' }) ++ @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' }) ++ @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' }) ++ salvar(@UploadedFile() arq: Express.Multer.File) { ++ return {estato: 'ok'}; ++ } }  
Enter fullscreen mode Exit fullscreen mode

após salvar o arquivo src/armazenamento/armazenamento.controller.ts, o terminal onde esta executando a API deve parecer com o console abaixo.
Note que foi adicionado mais um endpoint Mapped {/armazenamento, POST}

[19:37:49] File change detected. Starting incremental compilation... [19:37:49] Found 0 errors. Watching for file changes. [Nest] 155823 - 17/09/2024, 19:37:50 LOG [NestFactory] Starting Nest application... [Nest] 155823 - 17/09/2024, 19:37:50 LOG [InstanceLoader] AppModule dependencies initialized +23ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [InstanceLoader] UploadModule dependencies initialized +0ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [InstanceLoader] ArmazenamentoModule dependencies initialized +0ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [RoutesResolver] AppController {/}: +16ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [RouterExplorer] Mapped {/, GET} route +2ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [RoutesResolver] UploadController {/upload}: +0ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [RouterExplorer] Mapped {/upload/exemplo-simples, POST} route +1ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [RouterExplorer] Mapped {/upload/arquivos, POST} route +1ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [RoutesResolver] ArmazenamentoController {/armazenamento}: +0ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [RouterExplorer] Mapped {/armazenamento, POST} route +0ms [Nest] 155823 - 17/09/2024, 19:37:50 LOG [NestApplication] Nest application successfully started +2ms 
Enter fullscreen mode Exit fullscreen mode

para essa versão do endpoint, o exemplo de teste com a documentação swagger na figura abaixo e o resultado no console, também, abaixo.

{ "estato": "ok" } 
Enter fullscreen mode Exit fullscreen mode

objetivo: armazenar o arquivo recebido em disco local.

modificar o arquivo src/armazenamento/armazenamento.service.ts

import { Injectable } from '@nestjs/common'; ++import { promises as fs } from 'fs'; ++import * as path from 'path'; ++ ++class ImagemDto { ++ id: string; ++ nome: string; ++ tamanho: number; ++ mimetype: string; ++ encoding: string; ++ armazenamento: string; ++}  @Injectable() export class ArmazenamentoService { ++ dir = path.join(__dirname, '..', '..', 'uploads'); ++ imagens = new Array<ImagemDto>();  ++ async salvarEmDisco(arquivo: Express.Multer.File) { ++ const nomeCompleto = path.join(this.dir, arquivo.originalname); ++ ++ await fs.writeFile(nomeCompleto, arquivo.buffer); ++ this.imagens.push({ ++ id: arquivo.originalname, ++ nome: arquivo.originalname, ++ tamanho: arquivo.size, ++ mimetype: arquivo.mimetype, ++ encoding: arquivo.encoding, ++ armazenamento: nomeCompleto, ++ }); ++ return { ++ estado: 'ok', ++ dados: { ++ id: arquivo.originalname, ++ nome: arquivo.originalname, ++ }, ++ }; ++ } }  
Enter fullscreen mode Exit fullscreen mode

objetivo: ligar o endpoint (controller) ao processamento de armazenamento (service).

modificar o arquivo src/armazenamento/armazenamento.controller.ts

import {  Controller, Post, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { ArmazenamentoService } from './armazenamento.service'; import {  ApiBody, ApiConsumes, ApiOperation, ApiResponse, ApiTags, } from '@nestjs/swagger'; import { FileInterceptor } from '@nestjs/platform-express';  @Controller('armazenamento') @ApiTags('armazenamento') export class ArmazenamentoController {  constructor(private readonly armazenamentoService: ArmazenamentoService) {}   @Post() @UseInterceptors(FileInterceptor('imagem')) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { type: 'object', properties: { imagem: { type: 'string', format: 'binary', }, }, }, }) @ApiOperation({ summary: 'Upload de arquivo com armazenamento' }) @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' }) @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' }) salvar(@UploadedFile() arq: Express.Multer.File) { -- return {estato: 'ok'}; ++ return this.armazenamentoService.salvarEmDisco(arq);  } } 
Enter fullscreen mode Exit fullscreen mode

para a versão final do endpoint, o exemplo de teste com a documentação swagger na figura abaixo e o resultado no console, também, abaixo.

{ "estado": "ok", "dados": { "id": "Captura de tela de 2024-09-17 15-12-17.png", "nome": "Captura de tela de 2024-09-17 15-12-17.png" } } 
Enter fullscreen mode Exit fullscreen mode

6. codar o download de arquivo com armazenamento local

objetivo: criar o endpoint para upload de 1 arquivo com documentação swagger.

modificar o arquivo src/armazenamento/armazenamento.controller.ts

import {  Controller, ++ Get, ++ Param,  Post, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { ArmazenamentoService } from './armazenamento.service'; import {  ApiBody, ApiConsumes, ApiOperation, ApiResponse, ApiTags, } from '@nestjs/swagger'; import { FileInterceptor } from '@nestjs/platform-express';  @Controller('armazenamento') @ApiTags('armazenamento') export class ArmazenamentoController {  constructor(private readonly armazenamentoService: ArmazenamentoService) {}   @Post() @UseInterceptors(FileInterceptor('imagem')) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { type: 'object', properties: { imagem: { type: 'string', format: 'binary', }, }, }, }) @ApiOperation({ summary: 'Upload de arquivo com armazenamento' }) @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' }) @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' }) salvar(@UploadedFile() arq: Express.Multer.File) { return this.armazenamentoService.salvarEmDisco(arq); } ++ ++ @Get(':nome') ++ @ApiOperation({ summary: 'Endpoint para receber arquivo' }) ++ @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' }) ++ @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' }) ++ ler(@Param('nome') nome: string) { ++ return { estado: 'ok' }; ++ } }  
Enter fullscreen mode Exit fullscreen mode

após salvar o arquivo src/armazenamento/armazenamento.controller.ts, o terminal onde esta executando a API deve parecer com o console abaixo.
Note que foi adicionado mais um endpoint Mapped {/armazenamento/:nome, GET}

[19:51:57] File change detected. Starting incremental compilation... [19:51:57] Found 0 errors. Watching for file changes. [Nest] 156968 - 17/09/2024, 19:51:58 LOG [NestFactory] Starting Nest application... [Nest] 156968 - 17/09/2024, 19:51:58 LOG [InstanceLoader] AppModule dependencies initialized +12ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [InstanceLoader] UploadModule dependencies initialized +1ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [InstanceLoader] ArmazenamentoModule dependencies initialized +0ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RoutesResolver] AppController {/}: +15ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RouterExplorer] Mapped {/, GET} route +2ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RoutesResolver] UploadController {/upload}: +0ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RouterExplorer] Mapped {/upload/exemplo-simples, POST} route +1ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RouterExplorer] Mapped {/upload/arquivos, POST} route +0ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RoutesResolver] ArmazenamentoController {/armazenamento}: +1ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RouterExplorer] Mapped {/armazenamento, POST} route +0ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [RouterExplorer] Mapped {/armazenamento/:nome, GET} route +0ms [Nest] 156968 - 17/09/2024, 19:51:58 LOG [NestApplication] Nest application successfully started +5ms 
Enter fullscreen mode Exit fullscreen mode

para essa versão do endpoint, o exemplo de teste com a documentação swagger na figura abaixo e o resultado no console, também, abaixo.

{ "estato": "ok" } 
Enter fullscreen mode Exit fullscreen mode

objetivo: ler o arquivo do disco local.

modificar o arquivo src/armazenamento/armazenamento.service.ts

import { Injectable } from '@nestjs/common'; import { promises as fs } from 'fs'; import * as path from 'path';  class ImagemDto {  id: string; nome: string; tamanho: number; mimetype: string; encoding: string; armazenamento: string; }  @Injectable() export class ArmazenamentoService {  dir = path.join(__dirname, '..', '..', 'uploads'); imagens = new Array<ImagemDto>();   async salvarEmDisco(arquivo: Express.Multer.File) { const nomeCompleto = path.join(this.dir, arquivo.originalname);   await fs.writeFile(nomeCompleto, arquivo.buffer); this.imagens.push({ id: arquivo.originalname, nome: arquivo.originalname, tamanho: arquivo.size, mimetype: arquivo.mimetype, encoding: arquivo.encoding, armazenamento: nomeCompleto, }); return { estado: 'ok', dados: { id: arquivo.originalname, nome: arquivo.originalname, }, }; } ++ ++ async pegar(nome: string) { ++ const imagem = this.imagens.filter((item) => item.id === nome)[0]; ++ try { ++ const arquivo = await fs.readFile(imagem.armazenamento); ++ return { ++ estado: 'ok', ++ dados: { ++ informacao: imagem, ++ buffer: arquivo, ++ }, ++ }; ++ } catch (erro) { ++ return null; ++ } ++ } }  
Enter fullscreen mode Exit fullscreen mode

objetivo: ligar o endpoint (controller) ao processamento de armazenamento (service).

modificar o arquivo src/armazenamento/armazenamento.controller.ts

import {  Controller, Get, Param, Post, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { ArmazenamentoService } from './armazenamento.service'; import {  ApiBody, ApiConsumes, ApiOperation, ApiResponse, ApiTags, } from '@nestjs/swagger'; import { FileInterceptor } from '@nestjs/platform-express';  @Controller('armazenamento') @ApiTags('armazenamento') export class ArmazenamentoController {  constructor(private readonly armazenamentoService: ArmazenamentoService) {}   @Post() @UseInterceptors(FileInterceptor('imagem')) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { type: 'object', properties: { imagem: { type: 'string', format: 'binary', }, }, }, }) @ApiOperation({ summary: 'Upload de arquivo com armazenamento' }) @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' }) @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' }) salvar(@UploadedFile() arq: Express.Multer.File) { return this.armazenamentoService.salvarEmDisco(arq); }   @Get(':nome') @ApiOperation({ summary: 'Endpoint para receber arquivo' }) @ApiResponse({ status: 200, description: 'Arquivo enviado com sucesso.' }) @ApiResponse({ status: 404, description: 'Erro no envio do arquivo.' }) ler(@Param('nome') nome: string) { -- return { estado: 'ok' }; ++ return this.armazenamentoService.pegar(nome);  } }  
Enter fullscreen mode Exit fullscreen mode

para a versão final do endpoint, o exemplo de teste com a documentação swagger na figura abaixo e o resultado no console, também, abaixo.

{ "estado":"ok", "dados": { "informacao": { "id":"diatinf.png", "nome":"diatinf.png", "tamanho":132200, "mimetype":"image/png", "encoding":"7bit", "armazenamento":"/home/minora/minora/2024/upload-api/uploads/diatinf.png"}, "buffer":{ "type":"Buffer", "data": [137,80,78,71,13...] } } } } } 
Enter fullscreen mode Exit fullscreen mode

7. codar para adicionar exceção para arquivo não encontrado

a imagem abaixo mostra como é respondido pela API quando um arquivo não foi encontrado.
isso ocorre porque em service retorna null mas não é tratado devidamende em controller.

objetivo: lançar exceção quando o arquivo requisitado não for encontrado.

modificar o arquivo src/armazenamento/armazenamento.controller.ts

import {  Controller, Get, ++ NotFoundException,  Param, Post, UploadedFile, UseInterceptors, } from '@nestjs/common'; import {  ApiBody, ApiConsumes, ApiOperation, ApiResponse, ApiTags, } from '@nestjs/swagger'; import { FileInterceptor } from '@nestjs/platform-express'; import { ArmazenamentoService } from './armazenamento.service';  @Controller('armazenamento') @ApiTags('armazenamento') export class ArmazenamentoController {  constructor(private readonly armazenamentoService: ArmazenamentoService) {}   @Post() @UseInterceptors(FileInterceptor('imagem')) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { type: 'object', properties: { imagem: { type: 'string', format: 'binary', }, }, }, }) @ApiOperation({ summary: 'Upload de arquivo com armazenamento' }) @ApiResponse({ status: 201, description: 'Arquivo enviado com sucesso.' }) @ApiResponse({ status: 400, description: 'Erro no envio do arquivo.' }) salvar(@UploadedFile() arq: Express.Multer.File) { return this.armazenamentoService.salvarEmDisco(arq); }   @Get(':nome') @ApiOperation({ summary: 'Endpoint para receber arquivo' }) @ApiResponse({ status: 200, description: 'Arquivo enviado com sucesso.' }) @ApiResponse({ status: 404, description: 'Arquivo não encontrado.' }) -- ler(@Param('nome') nome: string) { ++ async ler(@Param('nome') nome: string) { -- return this.armazenamentoService.pegar(nome); ++ const resposta = await this.armazenamentoService.pegar(nome); ++ if (resposta) return resposta; ++ throw new NotFoundException('Arquivo não encontrado.');  } }  
Enter fullscreen mode Exit fullscreen mode

Top comments (0)