Um recurso muito importante quando vamos desenvolver aplicações web é ter a possibildiade de upload de arquivos, seja para ter uma foto de perfil em seu usuário ou um arquivo pdf em uma aplicação de gerenciamento de cursos, portanto, vamos aprender como fazer uploads com NestJS.
Primeiramente, devemos ter o ambiente Node.js e NestJS configurado, você encontra o passo a passo para essas configurações nos artigos a seguir:
Com o ambiente Node.js configurado e com o projeto NestJS criado, vamos configurar um banco de dados para salvar os dados do arquivo.
Curso Node.js - Fundamentos
Conhecer o cursoConfigurando banco de dados
Posteriormente, é necessário instalar o TypeORM e o sqlite, para isto basta aplicar o comando:
npm i @nestjs/typeorm typeorm sqlite3
Dentro do diretório /src
criaremos um arquivo chamado ormconfig.ts
, com a seguinte configuração:
import { DataSourceOptions } from 'typeorm';
export const config: DataSourceOptions = {
type: 'sqlite',
database: '.db/sql',
synchronize: true, // Obs: use synchronize: true somente em desenvolvimento.
entities: [__dirname + '/**/*.entity{.ts,.js}'],
};
Também será necessário apontar essa configuração no arquivo app.module.ts
, conforme abaixo:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { config } from './ormconfig';
@Module({
imports: [TypeOrmModule.forRoot(config)],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Por fim, o banco de dados está configurado.
Cadastro de dados de arquivos
Quando vamos fazer o upload com NestJS de um arquivo é necessário ter as informações desse arquivo salvas, por exemplo, em uma base de dados, para termos como acessar via uma url, entre outras possibilidades. Portanto, vamos criar os arquivos necessários para este processo. Vamos utilizar o comando no terminal:
nest g res files --no-spec
O Nest irá perguntar qual a camada de transporte que devemos usar, neste momento vamos utilizar a REST API. E na próxima pergunta pode aplicar y.
Teremos a seguinte estrutura:
src
|-Files
|--dto
|---createfile.dto.ts
|---update.file.dto.ts
|--entities
|---file.entity.ts
Devemos configurar agora a entidade de File
, onde vamos ter as seguintes propriedades:
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class File {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ nullable: false, unique: true })
fileName: string;
@Column({ nullable: false })
contentLength: number;
@Column({ nullable: false })
contentType: string;
@Column({ nullable: false })
url: string;
}
Desse modo vamos salvar algumas propriedades do arquivo no banco de dados, são elas:
- id: o número de identificação do arquivo;
- fileName: o nome do arquivo;
- contentLength: o tamanho do arquivo;
- contentType: o mimetype que identifica do arquivo;
- url: a url gerada para acessar o arquivo, caso seja necessário em uma aplicação web por exemplo.
Configurando diretório e nome do arquivo
Em seguida, vamos utilizar o Multer, que é um módulo do Express. Algumas utilizações do Multer podem ser conferidas na própria documentação do NestJS. Portanto, vamos instalar o pacote de tipagens do Multer:
npm i -D @types/multer
Agora podemos configurar em qual diretório será salvo o arquivo e também é interessante que ao salvar o arquivo, a aplicação gere um nome único, para não correr o risco de que usuários diferentes façam o upload de arquivos com nomes iguais.
Dando sequência, vamos criar um arquivo dentro do diretório /files
, com o nome multer-config.ts
:
import { diskStorage } from 'multer';
import * as path from 'path';
import { v4 as uuidv4 } from 'uuid';
const multerConfig = {
storage: diskStorage({
destination: './upload/files',
filename: (req, file, cb) => {
const fileName =
path.parse(file.originalname).name.replace(/\s/g, '') + '-' + uuidv4();
const extension = path.parse(file.originalname).ext;
cb(null, `${fileName}${extension}`);
},
}),
};
export default multerConfig;
Em seguida é necessário configurar a rota POST no files.controller.ts
, que irá receber as configurações do Multer e os dados do arquivo.
//files.controller.ts
import {
Controller,
Post,
UseInterceptors,
UploadedFile,
Req,
} from '@nestjs/common';
import { FilesService } from './files.service';
import { FileInterceptor } from '@nestjs/platform-express';
import multerConfig from './multer-config';
import { Request } from 'express';
@Controller('files')
export class FilesController {
constructor(private readonly filesService: FilesService) {}
@Post()
@UseInterceptors(FileInterceptor('arquivo', multerConfig))
uploadArquivo(
@UploadedFile() file: Express.Multer.File,
@Req() req: Request,
) {
return this.filesService.salvarDados(file, req);
}
}
Obs: renomeamos os métodos create()
do Controller e do Service para: uploadArquivo()
e salvarDados()
respectivamente, para ficar mais claro e facilitar a explicação, também excluimos os métodos que não serão utilizados.
Ou seja, utilizamos o decorator @UseInterceptors()
e passamos o FileInterceptor
como parâmetro, esse conjunto permite que o NestJS aplique as configurações do Multer para a propriedade arquivo
que será enviada pela requisição POST.
O decorator @UseInterceptors()
intercepta a requisição HTTP, e antes de executar o méotodo @uploadArquivo()
, aplica as configurações do interceptor fileInterceptor
, este interceptor possui dois parametros, o primeiro referente ao campo que será relacionado ao arquivo a ser feito o upload, e o segundo que leva as configurações do Multer. Após efetuar o upload e aplicar essa configurações, o decorator @UploadedFile()
injeta o File
no método uploadArquivo()
, onde este é o primeiro paramêtro do método salvarDados()
.
Utilizamos também o decorator @Req()
que será necessário para termos acesso a url, via requisição, que será salva nos dados do arquivo.
Configurando e salvando dados no banco
Neste momento o arquivo será salvo na pasta ./upload/files
, mas precisamos também popular os dados do arquivo no banco de dados, para isso vamos configurar o método salvarDados()
que se encontra no arquivo files.service.ts
, este método que estamos executando no método uploadArquivo()
do controller conforme acima.
Curso Nest.js - Fundamentos
Conhecer o cursoPortando, o arquivo file.service.ts
ficará da seguinte maneira:
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { File } from './entities/file.entity';
import { Request } from 'express';
@Injectable()
export class FilesService {
constructor(
@InjectRepository(File)
private fotoRepository: Repository,
) {}
async salvarDados(file: Express.Multer.File, req: Request) {
const arquivo = new File();
arquivo.fileName = file.filename;
arquivo.contentLength = file.size;
arquivo.contentType = file.mimetype;
arquivo.url = `${req.protocol}://${req.get('host')}/files/${file.filename}`;
return await this.fotoRepository.save(arquivo);
}
}
Precisamos analisar alguns pontos:
- Injeção de dependências e camada de repositório: neste caso, utilizamos a injeção de dependências para utilizar o repositório e desta forma ter acesso aos métodos do TypeORM, mais em específico o método
save()
, para salvar os dados do arquivo no banco de dados. No artigo criando o primeiro crud com NestJS entramos em mais detalhes sobre a utilização do TypeORM; - req.protocol: utilizamos o acesso à requisição para pegar criar a URL e pegar o protocolo de forma dinâmica, sendo HTTP ou HTTPS, por exemplo;
- req.get(‘host’): desta maneira temos acesso ao ‘host’ da aplicação, sendo possível criar um string referente a URL do arquivo, caso seja necessário o acesso do arquivo.
Por fim, precisamos atualizar o arquivo files.module.ts
, onde vamos configurar o TypeORM para utilizar a entidade File:
import { Module } from '@nestjs/common';
import { FilesService } from './files.service';
import { FilesController } from './files.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { File } from './entities/file.entity';
@Module({
imports: [TypeOrmModule.forFeature([File])],
controllers: [FilesController],
providers: [FilesService],
})
export class FilesModule {}
Testando upload com NestJS
Iremos utilizar a ferramenta insnomina para este teste, lembrando que ao gerar os arquivos relacionados a entidade Files
, o Controller é gerado com a rota “/files” por padrão, portanto, vamos criar uma requisição POST com a seguinte URL: http://localhost:3000/files.
No insomnia, logo abaixo do campo onde colocamos a rota, devemos clicar na primeira opção, será aberto um menu com várias opções, devemos selecionar a opção Multipart Form:
Irá exibir dois campos, um com um “placeholder” escrito “name”, aqui devemos digitar o nome do campo que configuramos no FileInterceptor
, neste caso, ‘arquivo’.
Ao lado temos um campo com o “placeholder” escrito “value”, devemos clicar na seta logo ao lado e selecionar File, e então, clicar em “chose File” e selecionar o arquivo que será efetuado o upload.
Ao clicar em Send, devemos receber o seguinte retorno:
CODE: 201 Created
{
"fileName": "nestjs-51fc8ba6-fbe5-4d4e-831e-d88db8432706.png",
"contentLength": 1650,
"contentType": "image/png",
"url": "http://localhost:3000//files/nestjs-51fc8ba6-fbe5-4d4e-831e-d88db8432706.png",
"id": 1
}
Veja que o insomnia irá retornar o código 201, referente a criação de um recurso, e o JSON referente aos dados que foram salvos, no caso o nome do arquivo, tamanho, tipo/extensão, url e o id.
Podemos verificar que no diretório .db
há o arquivo sql
, neste arquivo podemos visualizar o registro salvo:
Obs: este é um arquivo referente ao sqlite, no exemplo foi utilizado a extensão SQLite viewer compatível com o VS code, para visualizar as informações.
Também é criado automaticamente o diretório upload/files
, onde serão armazenados os arquivos de upload:
Upload de vários arquivos
Contudo, é possível realizar o upload de vários arquivos na mesma requisição. Logo, vamos criar outra requisição POST em nosso controller, adicionando a rota varios
para diferenciar da rota de upload de um único arquivo, aqui devemos utilizar o FileFiledsInterceptor
, para configurar o nome dos campos que serão utilizados na requisição e também o decorator @UploadedFiles()
, veja que este está no plural, ficando da seguinte maneira:
//files.controller.ts
import {
Controller,
Post,
UseInterceptors,
UploadedFile,
Req,
UploadedFiles,
} from '@nestjs/common';
import { FilesService } from './files.service';
import {
FileFieldsInterceptor,
FileInterceptor,
} from '@nestjs/platform-express';
import multerConfig from './multer-config';
import { Request } from 'express';
@Controller('files')
export class FilesController {
constructor(private readonly filesService: FilesService) {}
//método uploadFile();
@Post('varios')
@UseInterceptors(FileFieldsInterceptor([{ name: 'arquivos' }], multerConfig))
uploadVariosArquivos(
@UploadedFiles()
files: Express.Multer.File[],
@Req() req: Request,
) {
return this.filesService.salvarVariosDados(files['arquivos'], req);
}
E agora vamos criar o método salvarVariosDados()
no arquivo files.service.ts
, onde devemos salvar os dados no banco, veja que utilizamos o método .map()
para percorrer o array de arquivos que fizemos o upload, e então, setar as informações dos respectivos arquivos:
//files.service.ts
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { File } from './entities/file.entity';
import { Request } from 'express';
@Injectable()
export class FilesService {
constructor(
@InjectRepository(File)
private fotoRepository: Repository,
) {}
//método salvarDados();
async salvarVariosDados(files: Express.Multer.File[], req: Request) {
const arrayArquivos = files.map((file) => {
const arquivo = new File();
arquivo.fileName = file.filename;
arquivo.contentLength = file.size;
arquivo.contentType = file.mimetype;
arquivo.url = `${req.protocol}://${req.get('host')}/files/${
file.filename
}`;
return arquivo;
});
return await this.fotoRepository.save(arrayArquivos);
}
}
Veja que, diferente do método salvarDados()
, criamos um nov array chamado arrayArquivos
que retorna os arquivos do tipo File
com os dados de cada arquivo, obtidos pelo méotodo .map()
.
Vamos testar subir dois arquivos pela rota files/varios
:
Esperamos o seguinte retorno:
[
{
"fileName": "nestjs-af86b54d-7dee-4df5-8c13-b6f4b5f08adc.png",
"contentLength": 1650,
"contentType": "image/png",
"url": "http://localhost:3000/files/nestjs-af86b54d-7dee-4df5-8c13-b6f4b5f08adc.png",
"id": 19
},
{
"fileName": "react-f8a7163c-8250-4a47-aac1-f0e3aac90313.png",
"contentLength": 3173,
"contentType": "image/png",
"url": "http://localhost:3000/files/react-f8a7163c-8250-4a47-aac1-f0e3aac90313.png",
"id": 20
}
]
Conclusão
Neste artigo aprendemos a utilizar o Multer, módulo do Express que nos auxilia a trabalhar com armazenamento e upload de arquivos. Vale reforçar que focamos na possibilidade de armazenamento local. Porém, também é possível armazenar dados em serviços de hospedagem na nuvem, que será a temática do próximo artigo sobre upload com NestJS.
Caso tenha ficado alguma dúvida você pode acessar neste repositório do GitHub o código da aplicação utilizada neste artigo.
Por fim, caso queira aprender mais sobre NestJS saiba que aqui na TreinaWeb temos o curso Nest.js - Fundamentos que possui 02h07 de vídeos e um total de 18 exercícios. Conheça também nossos outros cursos de TypeScript.
Veja quais são os tópicos abordados durante o curso de Nest.js - Fundamentos:
- Conhecendo a estrutura;
- Utilizando Nest CLI
- Entendendo Rotas, Controllers e Views;;
- Conexão com banco de dados;
- Usando TypeORM;
- Template Engine.