Criando apps Full Stack com Angular, NestJS e Nx
Neste artigo vamos aprender a criar aplicações Full Stack, ou seja: Frontend e Backend, utilizando Angular e NestJS. E para centralizar dependências e interfaces, iremos utilizar a abordagem de monorepo com o Nx.
Por quê Angular e NestJS?
O Angular é um framework frontend desenvolvido pela Google, que é amplamente utilizado em muitas empresas. Ele possui a característica de ser opinativo, ou seja, ele oferece diversos padrões para a construção de aplicações que auxiliam na manutenção e adaptabilidade a longo prazo.
Já o NestJS é um framework backend NodeJS que é fortemente inspirado no Angular. Desenvolvedores que estão acostumados com o Angular irão se sentir em casa trabalhando com o NestJs por conta da similaridade dos conceitos.
Portanto, utilizar essas duas tecnologias pode ser uma excelente escolha para times Full Stack, pois a curva de aprendizado acaba sendo bem menor, e podemos compartilhar conceitos tanto no frontend quanto no backend.
Por quê Nx?
O Nx é um conjunto de ferramentas para monorepos. Ele oferece suporte a Angular e NestJS, permitindo a criação e gerenciamento de projetos em um único repositório, otimizando o fluxo de trabalho e facilitando o compartilhamento de código entre diferentes partes da aplicação.
Para saber mais sobre as vantagens em adotar o Nx, confira meu artigo:
Com ele podemos criar libs agnósticas de framework, somente TypeSctipt, que podem ser importadas tanto no nosso Frontend como em nosso Backend.
Criando um Workspace com Nx
Agora que entendemos os benefícios em utilizar essas tecnologias, vamos criar um Workspace Nx com o seguinte comando:
npx create-nx-workspace
O Nx fará uma série de perguntas para a criação do Workspace com base em nossas necessidades. Vamos escolher as seguintes opções:
✔ Where would you like to create your workspace? · fullstack-app
✔ Which stack do you want to use? · angular
✔ Integrated monorepo, or standalone project? · integrated
✔ Application name · frontend
✔ Which bundler would you like to use? · esbuild
✔ Default stylesheet format · scss
✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? · No
✔ Test runner to use for end to end (E2E) tests · cypress
✔ Do you want Nx Cloud to make your CI fast? · skip
Estamos optando por iniciar um Workspace com uma aplicação Angular chamada frontend, em forma de Integrated Monorepo. As demais perguntas são opcionais.
Após a criação e instalação das dependências, temos a seguinte estrutura de diretórios:
Temos algumas vantagens que este setup entrega:
- Setup de testes End-to-end com Cypress: Para garantir a qualidade logo de início, dentro da pasta
frontend-e2e
temos um ambiente todo configurado para criar testes end-to-end. - Jest pré-configurado: O Angular utiliza por padrão o framework de testes Jasmine integrado com o Test Runner Karma. Porém, ao utilizar o setup do Nx, a escolha padrão será o Jest. Simplesmente o mais popular framework de testes do ecossistema JavaScript.
- ESLint e Prettier pré-configurados: São duas ferramentas essenciais para qualquer projeto que deseja sempre manter a qualidade e padrões. Neste setup eles também já são pré-configurados.
Criando aplicação NestJS no Workspace Nx
Primeiramente precisamos instalar o plugin do NestJS para o Nx:
npx nx add @nx/nest
Agora criaremos a aplicação com o seguinte comando:
npx nx g @nx/nest:application --name=backend --frontendProject=frontend --directory=apps/backend --projectNameAndRootFormat=as-provided
Criamos a aplicação NestJS com o nome backend. Um ponto muito importante é a passagem da flag frontendProject
. Já estamos dizendo que nosso Backend deve olhar para a aplicação Frontend, adicionando uma configuração de proxy.
Na nossa estrutura de diretórios, temos na pasta apps
, as aplicações Frontend e Backend:
Criando biblioteca compartilhável
Vamos criar uma lib agnóstica de framework, somente com TypeScript, que terá a responsabilidade de compartilhar código entre as duas aplicações. Nesta camada podemos criar Interfaces, funções, contratos e qualquer tipo de código TypeScript.
npx nx g @nx/js:library interfaces --directory=libs/interfaces --bundler=none --unitTestRunner=jest --projectNameAndRootFormat=as-provided
Temos nossa estrutura pronta!
Integrando as camadas
Chegamos na parte mais interessante! Entenderemos como todas as camadas podem se comunicar.
Vamos expor um endpoint em nosso Backend com uma interface TypeScript. O Frontend consumirá esse endpoint utilizando a mesma interface, graças a nossa biblioteca compartilhável.
Criando Interface compartilhável
Iremos criar o arquivo message.ts
na Lib de Interfaces no diretório libs/interfaces/src/lib
.
export interface Message {
message: string;
}
Precisamos também exportá-la no arquivo index.ts
.
export * from './lib/message';
Integrando Interface no NestJS
Por padrão, a aplicação NestJS já vem com um módulo inicial chamado AppModule
. Portanto já temos a configuração pronta do nosso endpoint. Vamos somente importar nossa interface em AppService
no caminho apps/backend/src/app/app.service.ts
.
import { Message } from '@fullstack-app/interfaces';
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData(): Message {
return { message: 'Hello API' };
}
}
Perceba como foi realizado o import da interface Message
: estamos utilizando o Path Alias do TypeScript. Todas as bibliotecas criadas no Nx já vêm com seus paths automaticamente configurados. Você pode alterá-los no arquivo tsconfig.base.json
na raiz do repositório.
Integrando Interface no Angular
Agora vamos consumir este endpoint /api
no frontend. Primeiramente precisamos importar o provideHttpClient
no arquivo apps/frontend/src/app/app.config.ts
.
import { provideHttpClient } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
provideRouter(appRoutes),
],
};
O próximo passo é realizar a chamada HTTP. Para isso, vamos utilizar o componente inicial AppComponent
no caminho apps/frontend/src/app/app.component.ts
.
import { AsyncPipe, JsonPipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, inject } from '@angular/core';
import { Message } from '@fullstack-app/interfaces';
@Component({
standalone: true,
imports: [AsyncPipe, JsonPipe],
selector: 'app-root',
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent {
hello$ = inject(HttpClient).get<Message>('/api');
}
Criamos uma propriedade hello$
que vai receber um Observable do tipo Message
, que nada mais é do que a mesma interface utilizada no backend! Com esta abordagem evitamos quebra de contrato entre as aplicações. Se precisarmos alterar o contrato de qualquer endpoint do backend, automaticamente estaremos refletindo ele no frontend.
Precisamos somente mostrar em tela que a resposta do backend foi retornada corretamente. Basta então utilizar a propriedade no Template HTML em apps/frontend/src/app/app.component.html
.
<h1>{{ hello$ | async | json }}</h1>
Servindo as aplicações
Vamos rodar localmente nosso backend e frontend.
Em um terminal execute o seguinte comando para subir o backend:
npx nx serve backend
Agora em um novo terminal execute o seguinte comando para subir o frontend:
npx nx serve frontend
O frontend vai subir em localhost:4200
.
A comunicação entre as aplicações foi realizado com sucesso! 🎉
Na aba Network do DevTools conseguimos verificar com mais detalhes a chamada feita.
Analisando arquitetura do Workspace
O Nx possui um recurso extremamente poderoso chamado Dependency Graph. Ele tem a capacidade de analisar a relação entre todos os projetos contidos em um monorepo e montar uma árvore de dependências viva.
Para visualizar o grafo de dependências atual do nosso projeto, basta rodar o comando:
npx nx graph
Isso abrirá uma janela do navegador com uma visualização interativa do grafo do projeto do nosso worspace.
Veja como o grafo foi montado, indicando que nosso backend e frontend importam código da bibliotecas de interfaces.
Para monorepos com muitos projetos, essa funcionalidade é essencial para uma análise minuciosa das dependências. Podemos ir além, definindo barreiras e regras entre eles com Module Boundaries. Confira meu artigo para entender mais sobre:
Conclusão
Neste artigo aprendemos a criar aplicações Full Stack com Angular e NestJS. Entendemos como o Nx pode nos ajudar neste papel, criando camadas de comunicação entre as aplicações a partir de bibliotecas TypeScript.
Esta abordagem pode ser extremamente útil para times Full Stack, uma vez que os conceitos de ambas tecnologias são muito semelhantes, temos ganhos em quesitos como produtividade e manutenibilidade.
Confira o repositório de exemplo deste artigo:
Se curtiu esse conteúdo, me siga no Linkedin e no Twitter. 😀 Estou sempre postando conteúdos sobre Frontend e Tecnologia!