Informações gerais
- aula com endpoint para upload de arquivos com armazenamento em disco local para os alunos de programação orientada a serviços, do 4o ano de infoweb, do CNAT-IFRN
- repositório de código
- código final branch 03-upload-store ou zip
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
- pegar o código base
- acessar pasta do projeto e instalar bibliotecas do projeto
- executar a api
- incluir módulo nestjs para os novos endpoints
- codar o upload de arquivo com armazenamento local
- codar o download de arquivo com armazenamento local
- 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
3. executar a api
[upload-api] $ npm run start:dev
4. incluir módulo nestjs para os novos endpoints
[upload-api] $ npx @nestjs/cli generate resource armazenamento --no-spec
[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
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'};
++ }
}
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
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"
}
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,
++ },
++ };
++ }
}
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);
}
}
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"
}
}
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' };
++ }
}
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
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"
}
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;
++ }
++ }
}
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);
}
}
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...]
}
}
}
}
}
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.');
}
}
Top comments (0)