Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Nest.js: Persistindo dados com TypeORM e PostgreSQL

Nest.js: Persistindo dados com TypeORM e PostgreSQL

TypeORM - Apresentação

Olá! Boas-vindas ao curso de NestJS com TypeORM. Eu sou o Daniel e serei seu instrutor durante essa jornada.

Daniel é um homem de 25 anos, de pele clara, cabelos encaracolados longos, olhos castanho-escuros e barba por fazer. Está de camiseta preta com a silhueta do gato do GitHub estampada ao centro e headphones. Ao fundo, o seu quarto: um quadro em branco, uma estante com enfeites e uma iluminação azul.

Pré-requisitos

Esse curso faz parte da formação de NestJS. Então:

Esse último requisito é essencial, pois seguiremos com o projeto da loja, iniciado no primeiro curso, que trata de API Rest no NestJS e fundamentos do NestJS. Agora, basicamente, começaremos a salvar as informações de usuário e produto em um banco de dados.

Para quem é esse curso?

Esse curso é para você que está começando no desenvolvimento back-end e no Node ou já possui um conhecimento mais aprofundado em programação e, talvez, esteja migrando de tecnologia e começando no NestJS.

É importante frisar que trabalharemos somente com back-end neste curso.

O que aprenderemos?

Observação: Caso você queira trabalhar com outro banco no seu projeto, fique à vontade. Porém, no curso, ofereceremos o suporte para trabalhar com o Postgres.

E aí, vamos nessa?!

Nos encontramos no próximo vídeo, adentrando esse cenário que traçamos agora.

TypeORM - Configurando o TypeORM

Nessa sequência, começaremos a configuração do TypeORM no projeto. Mas, por que trabalharemos com TypeORM?

Considerando o curso inicial dessa informação, já temos o projeto da nossa loja. Nela, temos um cadastro de usuário e um cadastro de produto. Porém, hoje, essas informações não ficam persistidas em uma base de dados. O cadastro existe, mas é meramente ilustrativo - toda vez que fechamos a nossa aplicação, perdemos essas informações.

A ideia, então, é conseguir salvar essas informações no banco de dados. Para isso, trabalharemos com o conceito de ORM e, no NestJS, trabalharemos com o TypeORM: uma biblioteca que facilita a nossa integração com o banco de dados por meio de comandos do SQL.

O projeto

Para começar a configuração, nós abrimos o nosso projeto no VSCode. Basta clicar em "Arquivo > Abrir Pasta" e selecionar a pasta do projeto em que estamos trabalhando.

No menu Explorador, à esquerda da tela, e clicando na pasta "src", podemos observar que temos a mesma estrutura de projeto do curso anterior:

src

Começaremos a mexer tanto no módulo de "produto" quanto de "usuário", mas antes precisamos configurar o TypeORM.

Baixando as bibliotecas

Clicando em package.json, notamos que ainda não possuímos nenhuma dependência do TypeORM. Então, para começar, precisamos adicionar as bibliotecas do TypeORM no nosso projeto.

Faremos isso por um terminal, que fica à sua escolha. Aqui utilizaremos o terminal do próprio VSCode, abrindo-o pelo atalho "Ctrl + ’ ", que já inicializa dentro do diretório de trabalho - no caso, a pasta "loja".

Utilizaremos o seguinte comando:

npm install @nestjs/typeorm typeorm

Após digitar, pressionamos "Enter". Estamos adicionando duas bibliotecas no código, utilizando a notação do TypeScript para o TypeORM e, também, o TypeORM.

É normal demorar um tempo até a adição das bibliotecas ser concluída. Quando concluir, podemos fechar o terminal para limpar a tela.

Em seguida, abrimos o arquivo package.json no menu Explorador. Podemos notar que agora, na seção de "dependencies", temos tanto o typeorm quanto o @nestjs/typeorm, a notação do TypeScript utilizando o NestJS.

Temos a biblioteca do TypeORM no projeto. Feito isso, começaremos a configurá-lo.

Arquivo de configuração

Clicaremos na pasta "src" no menu Explorador com o botão direito do mouse e, em seguida, em "Nova Pasta…". Podemos chamá-la de "config", porque é nela que configuraremos o TypeORM.

Agora, clicamos nessa nova subpasta com o botão direito e, depois, em "Criar Arquivo…". O nome desse novo arquivo pode tanto seguir o nome do banco de dados com que trabalharemos como ser um nome genérico, por exemplo "db.config.service.ts".

No nosso caso, estamos trabalhando com o Postgres, um banco de dados comumente utilizado. Então, nomearemos esse arquivo como "postgres.config.service.ts". Tanto o banco de dados a ser utilizado quanto o nome do arquivo ficam a seu critério.

Pronto! Temos nosso arquivo inicial dentro da pasta "config".

Observação: Precisamos nos restringir a um domínio, e criar esse arquivo fora da pasta não seria uma boa prática.

Configurando o TypeORM

Nesse arquivo, antes de tudo, exportaremos a classe PostgresConfigService, conforme o nome do arquivo. Ela vai implementar o TypeOrmOptionsFactory, opção que podemos selecionar pelo autocomplete do VSCode, já que já possuímos a biblioteca.

Ao fazer isso, é feita a importação automática TypeOrmOptionsFactory. Caso isso não aconteça na sua máquina, basta importar manualmente. Então, nosso código fica assim:

postgres.config.service.ts

import { TypeOrmOptionsFactory } from '@nestjs/typeorm';

export class PostgresConfigService implements TypeOrmOptionsFactory {

}

Já podemos criar uma função dentro dessa classe, que será a função createTypeOrmOptions. Ou seja, criaremos algumas opções para o nosso TypeORM conseguir se conectar com o banco de dados. Vamos tipar essa função como TypeOrmModuleOptions, que também será importado automaticamente, e inserir um retorno:

import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';

export class PostgresConfigService implements TypeOrmOptionsFactory {
  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
        }
    }
}

Essa função nos retornará as configurações do banco, informações primordiais para conseguirmos nos conectar a ele. Então, passamos os seguintes parâmetros:

Agora, preencheremos essas configurações conforme o banco. Nós não temos o banco ainda, então vamos inserir algumas informações de exemplo apenas para o VSCode parar de reclamar os erros.

A primeira informação é o type. Se escrevermos uma aspa simples, ', receberemos várias sugestões de types. Só conseguimos utilizar o que estão nessas opções. O type é escolhido conforme o SGBD que queremos utilizar. Por exemplo, temos o mysql, o mongodb, e assim por diante. Estamos trabalhando com o Postgres; portanto, escrevemos 'postgres'.

O host é sempre dado como string, e podemos colocar como exemplo ilustrativo '127.0.0.1'. A porta pode ser 5432; o nome de usuário e senha podem ser 'root'; o banco será 'loja'.

Por fim, podemos anotar esse arquivo como @Injectable() no topo, logo acima da exportação da classe. Em seguida, realizamos a sua importação do @nestjs/common.

A classe, com a função e retorno, ficará assim:

import { Injectable } from '@nestjs/common';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';

@Injectable()
export class PostgresConfigService implements TypeOrmOptionsFactory {
  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'postgres',
      host: '127.0.0.1'
      port: 5432
      username: 'root'
      password: 'root'
      database: 'loja'
      entities: [],
      synchronize: true,
    };
  }
}

Talvez você tenha alguns erros apontados nos espaços em branco da indentação, relacionados com o eslint. Não precisamos nos preocupar com isso no momento, pois esses erros não têm relação com o que escrevemos.

Temos as informações do nosso banco de dados, a entidade que por enquanto está nula, e a opção de sincronização ativa. Esse arquivo está parcialmente resolvido!

Lembrando que não temos nenhum banco ainda, por isso essas informações são meramente fictícias. Criaremos um banco num momento oportuno.

Chamando a classe

Para finalizar essa configuração, clicamos no arquivo app.module.ts no menu Explorador, dentro da "src".

Temos um import de UsuarioModule e ProdutoModule nesse arquivo. Vamos quebrar essa linha, organizando esses imports em lista, e chamar uma nova importação: TypeOrmModule.forRootAsync({}). Dentro das chaves, colocamos um useClass, chamando a classe que acabamos de criar: PostgresConfigService.

Também podemos colocar um inject da mesma classe, pois estamos usando um @Injectable nela:

app.module.ts

import { Module } from '@nestjs/common';
import { ProdutoModule } from './produto/produto.module';
import { UsuarioModule } from './usuario/usuario.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostgresConfigService } from './config/postgres.config.service';

@Module({
  imports: [
    UsuarioModule,
    ProdutoModule,
    TypeOrmModule.forRootAsync({
      useClass: PostgresConfigService,
      inject: [PostgresConfigService],
    }),
  ],
})
export class AppModule {}

Pronto! Temos a configuração do TypeORM.

Claro, se rodarmos a aplicação agora, ela vai dar erro. Isso porque não temos nenhum banco no endereço fictício que indicamos em postgres.config.service.ts. Mas isso não é problema, porque somente criamos a configuração para criar o banco em um segundo momento.

É isso! Até mais.

TypeORM - Refatorando as configurações

No vídeo anterior, nós concluímos a configuração do TypeORM: já temos a classe de configuração para nos conectar ao banco, a postgres.config.service.ts, e também já configuramos o app.module.ts.

Mas, agora temos um detalhe referente às boas práticas. Estamos colocando informações sensíveis no meio do código: o nome de usuário (username) e a senha (password) do banco de dados.

Não é interessante que essas informações fiquem expostas no código, porque, por exemplo, subiremos esse repositório no GitHub - um local público. Ou seja: outra pessoa pode abrir esse repositório, ver o nome de usuário, senha e até o host, caso estivéssemos com o banco na nuvem, e acessá-lo.

O ideal, nesse caso, é trabalhar com variáveis de ambiente para não deixar informações sensíveis soltar no meio do código. É isso que faremos nesse vídeo!

Para isso, precisamos instalar uma biblioteca: @nestjs/config, um módulo do NestJS.

Instalando e configurando a biblioteca

Acionamos o terminal novamente, dentro do projeto "loja", e rodamos o seguinte comando:

npm install @nestjs/config

Ao terminar a instalação, fechamos o terminal. Realizaremos a configuração no arquivo app.module.ts novamente, o mesmo arquivo que colocamos o TypeOrmModule, que servirá para inicializar o módulo de configuração.

Podemos criar uma linha depois de ProdutoModule e chamar a função que será ConfigModule.forRoot({}). Dentro das chaves, colocamos o isGlobal: true para setar o ConfigModule como global. Também devemos importar o ConfigModule, a biblioteca que instalamos anteriormente.

Nosso arquivo ficará assim:

import { Module } from '@nestjs/common';
import { ProdutoModule } from './produto/produto.module';
import { UsuarioModule } from './usuario/usuario.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { PostgresConfigService } from './config/postgres.config.service';

@Module({
  imports: [
    UsuarioModule,
    ProdutoModule,
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    TypeOrmModule.forRootAsync({
      useClass: PostgresConfigService,
      inject: [PostgresConfigService],
    }),
  ],
})
export class AppModule {}

A configuração está, basicamente, preparada, e já a temos a nível global, então salvamos o arquivo com "Ctrl + S".

O que precisamos agora é que o nosso arquivo de configuração do Postgres consiga utilizar essa configuração para podermos remover isso do arquivo de configuração do Postgres.

Construtor

No menu Explorador, clicamos em "src > config > postgres.config.service.ts".

Para conseguirmos utilizar o módulo nesse arquivo, utilizamos um construtor na nossa classe. Criamos uma linha abaixo da exportação da classe e digitamos constructor().

A ideia do construtor é ter uma execução assim que a nossa classe for importada no app.module. Em outras palavras, quando ela for importada, queremos que o que estiver no construtor seja executado.

Dentro desse construtor, criaremos uma variável com private, chamada configService. Sua tipagem será ConfigService, cuja importação vem da biblioteca @nestjs/config que instalamos anteriormente. Depois, abrimos e fechamos chaves para que o erro do construtor suma:

postgres.config.service.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';

@Injectable()
export class PostgresConfigService implements TypeOrmOptionsFactory {
  constructor(private configService: ConfigService) {}

// código omitido

A partir disso, já conseguimos utilizar as configurações do próprio ConfigService.

Atualizando as configurações do banco

Então, podemos utilizar essas configurações no host da seguinte maneira: digitamos this.configService.get<string>(''), pois queremos pegar um valor do tipo string. Passaremos como parâmetro a referência para pegar esse valor. Então:

host: this.configService.get<string>('')

Faremos isso com todos os outros campos. Podemos copiar essa linha e colar para todos os parâmetros, apenas mudando o tipo string para number na porta, porque o valor desse campo é numérico, resultando em:

postgres.config.service.ts

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'postgres',
      host: this.configService.get<string>(''),
      port: this.configService.get<number>(''),
      username: this.configService.get<string>(''),
      password: this.configService.get<string>(''),
      database: this.configService.get<string>(''),
      entities: [''],
      synchronize: true,
    };
  }
}

Fizemos 50% da configuração. Agora, precisamos referenciar os valores que queremos buscar.

Para isso, voltamos ao Explorador e clicamos com o botão direito na pasta "loja" (fora de "src") e, em seguida, em "Novo Arquivo...". Vamos nomear esse novo arquivo como .env (pronunciado "dot env" ou "ponto env"), e será nele que nossas configurações ficarão.

Então, por exemplo: no arquivo postgres.config.service, tínhamos o host definido como 127.0.0.1. Essa informação não vai ficar mais nesse arquivo, mas no .env. Esse arquivo, por boas práticas, não é enviado para o GitHub. Ou seja, a informação só existe localmente na máquina.

Então, criaremos as referências nele. Como:

.env

DB_HOST=127.0.0.1

Agora, para referenciá-lo, basta voltar no arquivo postgres.config.service e colocar a referência como string entre os parênteses:

postgres.config.service.ts

host: this.configService.get<string>('DB_HOST')

Ou seja: queremos que o configService leia o arquivo .env, um arquivo padrão, onde temos o DB_HOST, chave cujo valor é 127.0.0.1, agora não mais fixo no código.

Agora, só precisamos seguir a mesma lógica para os outros valores:

.env

DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=root
DB_PASSWORD=root
DB_NAME=loja

Usamos todas as informações de antes, criando as chaves com nomes comumente utilizados por boa prática. Porém, podemos definir outros nomes caso seja necessário.

O importante é que o nome definido seja o mesmo referenciado no arquivo de configuração do banco.

Agora, inserimos as referências:

postgres.config.service.ts

// código omitido

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'postgres',
      host: this.configService.get<string>('DB_HOST'),
      port: this.configService.get<number>('DB_PORT'),
      username: this.configService.get<string>('DB_USERNAME'),
      password: this.configService.get<string>('DB_PASSWORD'),
      database: this.configService.get<string>('DB_NAME'),
      entities: [],
      synchronize: true,
    };
  }

Um último detalhe interessante: vamos verificar se o arquivo .env consta em .gitignore. Se pressionarmos "Ctrl + F" e digitarmos ".env" na barra de busca, não teremos nenhum resultado.

Precisamos adicioná-lo, porque esse arquivo não vai para o GitHub por uma questão de segurança. Estando no arquivo .gitignore, ele será ignorado no upload para o repositório público. No fim do arquivo, então, digitamos:

.gitignore

// omitido

.env

Próximos passos

Para finalizar, vamos executar o projeto e entender os erros atuais para traçar nossos próximos passos. Para tanto, abrimos novamente o terminal e rodamos o seguinte comando dentro da pasta "loja":

npm start

Automaticamente, receberemos algumas reclamações. Por exemplo:

Postgres package has not been found installed.

Ou seja, o pacote do Postgres não está instalado. A sugestão dada é rodar o seguinte comando:

npm install pg 

O pg é o módulo do Postgres. No nosso arquivo postgres.config.service.ts, estamos usando utilizando o postgres como tipo do banco. Para isso, é necessário que o nosso projeto conheça o Postgres. Por isso o erro foi apontado e foi pedida a instalação - e isso aconteceria com qualquer outro banco.

Então, vamos rodar o comando acima para finalizar essa configuração. É um pacote bem enxuto e de instalação rápida. Ao finalizar, podemos tentar executar nosso projeto novamente com npm start.

O próximo erro apontado é:

Password authentication failed for user "root".

Já prevíamos esse erro, que acontece porque não temos o servidor do Postgres. A partir dos próximos vídeos, veremos um pouco mais sobre ORM e começaremos a realizar a correção desse problema criando, de fato, um banco de dados.

Até lá!

Sobre o curso Nest.js: Persistindo dados com TypeORM e PostgreSQL

O curso Nest.js: Persistindo dados com TypeORM e PostgreSQL possui 170 minutos de vídeos, em um total de 45 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas