Segunda parte do guia de JTE com Spring Boot: avance do primeiro template para páginas tipadas, componentes reutilizáveis, layouts e uma configuração adequada para produção.
Na primeira parte deste conteúdo, vimos como colocar o JTE com Spring Boot para funcionar e renderizar uma página simples. Agora vamos avançar para o ponto em que um motor de templates realmente começa a mostrar seu valor em uma aplicação: páginas com modelos tipados, listas, condicionais, componentes reutilizáveis e layouts.
O objetivo desta continuação é sair do tradicional “Hello World” e montar uma pequena aplicação server-side com uma estrutura que poderia evoluir para um sistema real.
Nesta parte, você aprenderá:
- como usar parâmetros tipados em templates JTE;
- como trabalhar com registros Java como View Models;
- como criar condicionais com
@if; - como percorrer coleções com
@for; - como criar componentes reutilizáveis;
- como criar um layout compartilhado;
- como passar blocos de conteúdo entre templates;
- como o escaping HTML ajuda na segurança;
- como organizar o projeto;
- como configurar desenvolvimento e produção;
- o que muda entre Spring Boot 3 e Spring Boot 4.
Os exemplos deste artigo usam a linha atual do JTE 3.2.x. Em setembro de 2026, a documentação oficial apresenta a versão 3.2.4 nos starters para Spring Boot 3 e 4.
Antes de continuar, vale alinhar a configuração com a documentação atual do projeto.
No Spring Boot 4, o starter utilizado é:
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-spring-boot-starter-4</artifactId>
<version>3.2.4</version>
</dependency>Já no Spring Boot 3, a documentação atual apresenta:
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-spring-boot-starter-3</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte</artifactId>
<version>3.2.4</version>
</dependency>No Spring Boot 4, o JTE também aparece no ecossistema do Spring Initializr como uma opção de template engine.
Por padrão, os templates ficam em:
src/main/jte
Essa é uma diferença importante em relação a engines que tradicionalmente usam:
src/main/resources/templates
A configuração padrão pode ser alterada, mas neste artigo vamos seguir a convenção oficial.
Vamos montar um pequeno catálogo de cursos.
A estrutura será semelhante a esta:
src/
└── main/
├── java/
│ └── com/
│ └── exemplo/
│ └── cursos/
│ ├── CursosApplication.java
│ ├── controller/
│ │ └── CursoController.java
│ └── viewmodel/
│ ├── CursoView.java
│ └── CursosPage.java
├── jte/
│ ├── cursos.jte
│ └── layout/
│ ├── page.jte
│ └── cursoCard.jte
└── resources/
└── application.properties
A ideia é separar:
- código Java;
- modelos usados pela interface;
- templates de página;
- componentes;
- layout compartilhado.
Esse tipo de organização ajuda quando a quantidade de páginas aumenta.
Uma das características mais interessantes do JTE é trabalhar diretamente com tipos Java.
Vamos representar um curso com um record:
package com.exemplo.cursos.viewmodel;
public record CursoView(
Long id,
String nome,
String categoria,
double preco,
boolean destaque
) {
}Agora criamos o modelo da página:
package com.exemplo.cursos.viewmodel;
import java.util.List;
public record CursosPage(
String titulo,
String usuario,
List<CursoView> cursos
) {
}Por que criar um modelo específico para a view?
Porque o template não precisa receber diretamente uma entidade JPA cheia de campos, relacionamentos e detalhes internos.
Podemos expor apenas o que a interface realmente precisa.
Isso reduz acoplamento e evita situações como:
Entidade do banco
↓
carregamento lazy
↓
template acessa relacionamento
↓
consulta inesperada
Um View Model deixa o contrato da página explícito.
Agora montamos um controller Spring MVC.
package com.exemplo.cursos.controller;
import com.exemplo.cursos.viewmodel.CursoView;
import com.exemplo.cursos.viewmodel.CursosPage;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class CursoController {
@GetMapping("/cursos")
public String cursos(Model model) {
List<CursoView> cursos = List.of(
new CursoView(
1L,
"Spring Boot do Zero",
"Java",
149.90,
true
),
new CursoView(
2L,
"APIs REST com Java",
"Back-End",
129.90,
false
),
new CursoView(
3L,
"Docker para Desenvolvedores",
"DevOps",
99.90,
true
)
);
CursosPage page = new CursosPage(
"Catálogo de Cursos",
"Michel",
cursos
);
model.addAttribute("page", page);
return "cursos";
}
}Observe o retorno:
return "cursos";Com o starter configurado, não precisamos retornar:
return "cursos.jte";O ViewResolver do JTE localiza o template correspondente.
Crie:
src/main/jte/cursos.jte
O início do arquivo pode ficar assim:
@import com.exemplo.cursos.viewmodel.CursosPage
@param CursosPage page
<h1>${page.titulo()}</h1>
<p>Olá, ${page.usuario()}!</p>O ponto mais importante está aqui:
@param CursosPage pageO template declara explicitamente qual tipo espera receber.
Isso significa que o JTE consegue validar expressões durante a compilação.
Se escrevermos por engano:
{page.nomeInexistente()}o erro pode ser detectado antes de chegar ao usuário final.
Essa abordagem é diferente de engines muito dependentes de expressões dinâmicas avaliadas apenas em tempo de execução.
Vamos mostrar uma mensagem diferente quando houver cursos cadastrados.
@if(page.cursos().isEmpty())
<p>Nenhum curso disponível no momento.</p>
@else
<p>
Encontramos ${page.cursos().size()} cursos.
</p>
@endifA estrutura lembra Java:
@if(...)
@else
@endif
Também existe:
@elseif
Exemplo:
@if(page.cursos().size() >= 10)
<p>Catálogo grande.</p>
@elseif(page.cursos().size() >= 5)
<p>Catálogo médio.</p>
@else
<p>Catálogo compacto.</p>
@endifA sintaxe permanece próxima da linguagem Java, o que reduz a necessidade de aprender uma linguagem de expressão completamente diferente.
Agora vamos mostrar todos os cursos.
@for(var curso : page.cursos())
<article>
<h2>${curso.nome()}</h2>
<p>Categoria: ${curso.categoria()}</p>
<p>Preço: R$ ${curso.preco()}</p>
</article>
@endforTambém podemos combinar loop e condição:
@for(var curso : page.cursos())
<article>
<h2>${curso.nome()}</h2>
@if(curso.destaque())
<strong>Curso em destaque</strong>
@endif
<p>${curso.categoria()}</p>
</article>
@endforDesde o JTE 3, o @for também pode usar um @else para o estado vazio:
@for(var curso : page.cursos())
<p>${curso.nome()}</p>
@else
<p>Nenhum curso encontrado.</p>
@endforIsso elimina um @if separado em muitos casos.
Quando repetimos o mesmo HTML em várias páginas, podemos criar outro template e chamá-lo como componente.
Crie:
src/main/jte/layout/cursoCard.jte
Conteúdo:
@import com.exemplo.cursos.viewmodel.CursoView
@param CursoView curso
<article class="curso-card">
<h2>${curso.nome()}</h2>
<p>
Categoria:
<strong>${curso.categoria()}</strong>
</p>
<p>
R$ ${String.format("%.2f", curso.preco())}
</p>
@if(curso.destaque())
<span class="badge">Destaque</span>
@endif
</article>No template principal:
@for(var curso : page.cursos())
@template.layout.cursoCard(curso = curso)
@endforO caminho:
layout/cursoCard.jte
vira a chamada:
@template.layout.cursoCard(...)
Isso permite criar uma biblioteca de componentes server-side.
Por exemplo:
layout/
├── button.jte
├── alert.jte
├── navbar.jte
├── pagination.jte
├── cursoCard.jte
└── page.jte
O JTE permite passar parâmetros pelo nome.
Exemplo:
@template.layout.cursoCard(
curso = curso
)Isso melhora a leitura quando um componente recebe vários argumentos.
Imagine:
@template.layout.alert(
titulo = "Atenção",
mensagem = "O curso está indisponível.",
tipo = "warning"
)A chamada fica mais clara do que depender apenas da posição de cada parâmetro.
Também é possível declarar valores padrão.
Exemplo de componente:
@param String texto
@param String tipo = "primary"
<button class="btn btn-${tipo}">
${texto}
</button>Agora podemos chamar:
@template.layout.button(
texto = "Comprar"
)Ou sobrescrever:
@template.layout.button(
texto = "Excluir",
tipo = "danger"
)Esse recurso ajuda a criar componentes reutilizáveis sem multiplicar versões quase iguais do mesmo template.
Uma aplicação real costuma repetir:
<!DOCTYPE html>;<html>;<head>;- menu;
- rodapé;
- arquivos CSS;
- metadados.
Não queremos copiar essa estrutura em todas as páginas.
Para isso, podemos usar gg.jte.Content.
Crie:
src/main/jte/layout/page.jte
Conteúdo:
@import gg.jte.Content
@param String title
@param Content content
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<title>${title}</title>
</head>
<body>
<header>
<nav>
<a href="/">Início</a>
<a href="/cursos">Cursos</a>
</nav>
</header>
<main>
${content}
</main>
<footer>
<p>Projeto de exemplo com Spring Boot e JTE.</p>
</footer>
</body>
</html>Agora o template cursos.jte pode utilizar esse layout:
@import com.exemplo.cursos.viewmodel.CursosPage
@param CursosPage page
@template.layout.page(
title = page.titulo(),
content = @
<h1>${page.titulo()}</h1>
<p>Bem-vindo, ${page.usuario()}.</p>
<section class="cursos">
@for(var curso : page.cursos())
@template.layout.cursoCard(
curso = curso
)
@else
<p>Nenhum curso encontrado.</p>
@endfor
</section>
)O trecho:
@
...
representa um bloco de conteúdo que será passado para o layout.
Com isso, cada página define apenas seu conteúdo específico.
Sem composição, uma aplicação cresce assim:
pagina1.jte -> HTML completo
pagina2.jte -> HTML completo copiado
pagina3.jte -> HTML completo copiado
pagina4.jte -> HTML completo copiado
Depois de alguns meses, alterar o menu significa modificar vários arquivos.
Com layouts:
layout/page.jte
↑
├── cursos.jte
├── usuarios.jte
├── pedidos.jte
└── dashboard.jte
Agora uma mudança no cabeçalho pode ser feita em um único lugar.
O mesmo princípio vale para componentes menores.
Um motor de templates precisa tratar cuidadosamente conteúdo vindo do usuário.
Imagine:
String nome = "<script>alert('xss')</script>";Se esse valor for inserido diretamente em HTML sem escaping, a página pode ficar vulnerável a Cross-Site Scripting (XSS).
O JTE foi projetado com escaping HTML sensível ao contexto.
Quando utilizamos:
{page.usuario()}o valor não deve ser interpretado simplesmente como HTML arbitrário.
Isso é uma vantagem importante.
Contudo, escaping de template não substitui uma estratégia completa de segurança.
Continue aplicando:
- validação de entrada;
- Spring Security;
- proteção CSRF quando aplicável;
- Content Security Policy;
- cookies seguros;
- controle de autorização;
- tratamento adequado de URLs;
- sanitização quando HTML fornecido pelo usuário realmente precisa ser permitido.
Segurança deve existir em várias camadas.
Como o JTE permite expressões Java, existe uma tentação:
Se posso escrever Java no template, posso colocar qualquer lógica aqui.
Tecnicamente, muita coisa é possível.
Arquiteturalmente, isso não significa que seja uma boa ideia.
Evite:
template
↓
calcula desconto
↓
consulta permissão
↓
decide regra fiscal
↓
transforma entidade
Prefira:
Service
↓
regra de negócio
Controller
↓
monta View Model
JTE
↓
renderiza a apresentação
O template pode decidir:
- mostrar ou esconder um botão;
- aplicar uma classe CSS;
- percorrer uma lista;
- mostrar estado vazio;
- exibir um texto alternativo.
Mas a regra central do negócio deve continuar no Java apropriado.
Vamos separar a obtenção dos cursos.
@Service
public class CursoService {
public List<CursoView> listar() {
return List.of(
new CursoView(
1L,
"Spring Boot do Zero",
"Java",
149.90,
true
),
new CursoView(
2L,
"APIs REST com Java",
"Back-End",
129.90,
false
)
);
}
}Controller:
@Controller
public class CursoController {
private final CursoService cursoService;
public CursoController(CursoService cursoService) {
this.cursoService = cursoService;
}
@GetMapping("/cursos")
public String cursos(Model model) {
CursosPage page = new CursosPage(
"Catálogo de Cursos",
"Michel",
cursoService.listar()
);
model.addAttribute("page", page);
return "cursos";
}
}A responsabilidade fica mais clara:
CursoService
↓
dados e regras
CursoController
↓
montagem da página
cursos.jte
↓
HTML
Durante o desenvolvimento, a documentação do JTE permite habilitar:
gg.jte.developmentMode=trueNesse modo, o JTE acompanha alterações nos templates e pode recompilá-los.
É importante lembrar:
O modo de desenvolvimento requer um JDK.
Isso ocorre porque os templates precisam ser compilados durante o desenvolvimento.
Uma configuração local pode ficar assim:
gg.jte.developmentMode=true
gg.jte.templateLocation=src/main/jte
gg.jte.templateSuffix=.jtePara produção, a recomendação é diferente.
Os templates podem ser pré-compilados durante o build.
A propriedade utilizada pelo starter é:
gg.jte.usePrecompiledTemplates=trueNesse cenário:
build
↓
templates JTE compilados
↓
artefato da aplicação
↓
produção carrega templates prontos
Isso traz vantagens:
- reduz trabalho durante a inicialização;
- evita compilar templates em produção;
- permite executar com JRE sem depender da compilação dinâmica;
- antecipa erros de template para o pipeline de build.
O JTE possui plugins oficiais para Maven e Gradle destinados a essa etapa.
A própria documentação recomenda manter a versão do plugin alinhada com a versão da dependência JTE.
Por exemplo:
<properties>
<jte.version>3.2.4</jte.version>
</properties>E reutilizar:
<version>${jte.version}</version>tanto nas dependências quanto no plugin.
A configuração deve representar claramente o ambiente.
Desenvolvimento:
gg.jte.developmentMode=true
gg.jte.usePrecompiledTemplates=falseProdução:
gg.jte.developmentMode=false
gg.jte.usePrecompiledTemplates=trueNão faz sentido pedir simultaneamente:
- recompilação dinâmica;
- somente templates pré-compilados.
Uma boa estratégia é utilizar profiles.
gg.jte.developmentMode=true
gg.jte.usePrecompiledTemplates=falsegg.jte.developmentMode=false
gg.jte.usePrecompiledTemplates=trueAssim o comportamento muda junto com o ambiente.
Hoje o JTE possui starters separados.
| Versão do Spring Boot | Starter |
|---|---|
| Spring Boot 3.x | jte-spring-boot-starter-3 |
| Spring Boot 4.x | jte-spring-boot-starter-4 |
No Boot 4, o starter atual também oferece uma variante -core para cenários em que você quer a configuração do TemplateEngine, mas não precisa registrar o ViewResolver MVC.
Isso pode ser útil quando a aplicação usa o motor de templates para gerar:
- e-mails HTML;
- arquivos;
- relatórios;
- fragmentos;
- conteúdo fora do fluxo tradicional de views MVC.
Embora o exemplo seja uma aplicação MVC, um template engine também pode ser útil para produzir outros conteúdos.
Exemplos:
Template JTE
↓
HTML de e-mail
Template JTE
↓
documento HTML
Template JTE
↓
fragmento para HTMX
Template JTE
↓
página SSR
Template JTE
↓
conteúdo textual estruturado
Quando usamos apenas o TemplateEngine, não precisamos necessariamente retornar uma view por um controller.
Uma dúvida natural é:
O JTE substitui o Thymeleaf?
A melhor resposta é: depende do projeto.
O JTE tende a agradar equipes que valorizam:
- templates tipados;
- sintaxe próxima de Java/Kotlin;
- compilação;
- alta performance;
- componentes simples;
- feedback de erro mais cedo;
- menor dependência de uma linguagem de expressão própria.
O Thymeleaf possui um ecossistema muito maduro e uma filosofia de templates HTML naturais.
Ele pode ser interessante quando:
- a equipe já domina Thymeleaf;
- o projeto possui muitos templates existentes;
- integrações específicas do ecossistema são importantes;
- designers precisam trabalhar diretamente com HTML que continua visualmente próximo de um documento normal.
Não existe obrigação de migrar uma aplicação saudável apenas porque surgiu outra opção.
A decisão deve considerar:
- manutenção;
- experiência da equipe;
- desempenho;
- quantidade de páginas;
- necessidade de tipagem;
- integração com o projeto atual.
Considere:
public record CursoView(
String nome,
double preco
) {
}E o template:
<h2>${curso.nome()}</h2>Se o método mudar para:
titulo()o template precisa ser atualizado.
Como existe um contrato tipado, ferramentas de desenvolvimento e a compilação conseguem ajudar a encontrar o problema.
Em aplicações grandes, isso é relevante porque páginas deixam de ser apenas arquivos de texto interpretados sem relação explícita com o modelo Java.
O projeto JTE disponibiliza suporte para IntelliJ IDEA.
Esse tipo de integração melhora a experiência com:
- autocomplete;
- navegação;
- refatoração;
- destaque de sintaxe;
- reconhecimento de parâmetros;
- código Java dentro dos templates.
Quanto mais o projeto depende de templates tipados, mais valioso se torna ter uma IDE que entenda essa relação.
Podemos testar se o controller retorna a view esperada e cria o modelo.
Exemplo conceitual com Spring MVC Test:
@WebMvcTest(CursoController.class)
class CursoControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CursoService cursoService;
@Test
void deveExibirPaginaDeCursos() throws Exception {
when(cursoService.listar())
.thenReturn(List.of());
mockMvc.perform(get("/cursos"))
.andExpect(status().isOk())
.andExpect(view().name("cursos"))
.andExpect(model().attributeExists("page"));
}
}Podemos também adicionar um teste de integração que valide trechos do HTML renderizado.
Isso ajuda a detectar:
- template inexistente;
- parâmetros incompatíveis;
- layout quebrado;
- alteração inesperada na página.
Conforme o sistema cresce, uma estrutura possível é:
src/main/jte/
├── layout/
│ ├── page.jte
│ ├── navbar.jte
│ └── footer.jte
│
├── components/
│ ├── alert.jte
│ ├── button.jte
│ ├── pagination.jte
│ └── modal.jte
│
├── cursos/
│ ├── index.jte
│ ├── detalhes.jte
│ └── form.jte
│
├── usuarios/
│ ├── index.jte
│ └── perfil.jte
│
└── dashboard/
└── index.jte
O controller pode retornar:
return "cursos/index";Isso mantém as páginas agrupadas por domínio.
Na configuração padrão atual:
src/main/jte
e não necessariamente:
src/main/resources/templates
Prefira:
return "cursos";em vez de acoplar o controller à extensão:
return "cursos.jte";Se o template depende de um objeto, declare explicitamente:
@param CursosPage pageController:
model.addAttribute("page", page);Template:
@param CursosPage pageOs nomes precisam representar o mesmo contrato.
Não transforme o template em uma segunda camada de serviço.
Produção deve preferir templates pré-compilados.
Quando usar plugin Maven ou Gradle do JTE, mantenha a versão alinhada com a biblioteca.
Como exercício, transforme nosso catálogo em uma pequena aplicação.
Implemente:
- página de listagem;
- página de detalhes;
- componente de card;
- componente de botão;
- layout principal;
- estado vazio;
- destaque visual para cursos promocionais;
- View Model próprio para cada página;
- service separado do controller;
- profile de desenvolvimento e produção.
Depois crie:
GET /cursos
GET /cursos/{id}
Na página de detalhes, mostre:
- nome;
- categoria;
- descrição;
- preço;
- status;
- botão de matrícula.
Crie:
public record CursosPage(
String titulo,
List<CursoView> cursos,
int paginaAtual,
int totalPaginas
) {
}Depois, no JTE:
<nav>
@for(int pagina = 1; pagina <= page.totalPaginas(); pagina++)
<a href="/cursos?page=${pagina}">
${pagina}
</a>
@endfor
</nav>Em seguida, transforme essa navegação em:
components/pagination.jte
O desafio ensina três conceitos ao mesmo tempo:
- loops;
- parâmetros;
- componentes.
JTE combina naturalmente com aplicações server-side e também pode ser usado em arquiteturas HTML-over-the-wire.
Uma evolução interessante é adicionar HTMX.
Nesse modelo:
Browser
↓
requisição HTTP
↓
Spring MVC
↓
JTE
↓
fragmento HTML
↓
HTMX substitui parte da página
Isso permite construir interfaces interativas sem transformar obrigatoriamente toda a aplicação em uma SPA.
Um próximo artigo pode explorar justamente:
Spring Boot + JTE + HTMX.
JTE merece consideração quando você deseja:
- renderização server-side;
- páginas HTML rápidas;
- contratos tipados entre Java e template;
- poucas abstrações entre template e linguagem;
- componentes simples;
- compilação antecipada;
- boa integração com Spring Boot.
Ele pode ser especialmente interessante em:
- sistemas administrativos;
- dashboards;
- SaaS server-side;
- portais;
- intranets;
- CRUDs;
- aplicações que usam HTMX;
- páginas que não precisam de uma SPA completa.
Considere outra arquitetura quando:
- a aplicação é essencialmente uma SPA rica;
- o front-end possui uma equipe separada totalmente baseada em React, Angular ou Vue;
- o projeto já possui grande investimento em outro template engine;
- a equipe depende de integrações específicas inexistentes no JTE;
- a interface exige grande quantidade de estado no cliente.
Tecnologia deve resolver o problema do projeto, não apenas seguir novidade.
Nesta segunda parte, evoluímos de um template simples para uma estrutura mais próxima de uma aplicação real.
Aprendemos:
@parampara modelos tipados;@if,@elseife@else;@fore estado vazio;- chamadas com
@template; - parâmetros nomeados;
- parâmetros com valores padrão;
gg.jte.Content;- layouts compartilhados;
- View Models;
- escaping HTML;
- separação entre regra de negócio e apresentação;
- development mode;
- templates pré-compilados;
- diferenças entre os starters do Spring Boot 3 e 4.
O ponto principal é perceber que JTE não é apenas uma forma de colocar valores dentro de HTML.
Ele permite tratar templates como parte tipada da aplicação Java.
Na próxima etapa, podemos avançar para uma aplicação mais interativa com:
Spring Boot + JTE + HTMX: criando páginas dinâmicas sem SPA.
Podemos implementar:
- busca sem recarregar a página inteira;
- filtros;
- paginação;
- formulários;
- validação;
- fragments JTE;
- loading states;
- modais;
- atualização parcial do HTML.
Essa combinação mantém grande parte da lógica no servidor e usa JavaScript apenas quando realmente necessário.
- Onde os templates JTE ficam por padrão?
- Para que serve
@param? - Qual vantagem existe em usar View Models?
- Como funciona
@for? - Como criar um componente reutilizável?
- Para que serve
gg.jte.Content? - Qual a diferença entre development mode e templates pré-compilados?
- Por que não devemos colocar regra de negócio no template?
- Qual starter usar com Spring Boot 3?
- Qual starter usar com Spring Boot 4?
- Como layouts reduzem duplicação?
- Por que a tipagem ajuda em refatorações?
O JTE com Spring Boot oferece uma abordagem moderna para renderização server-side em Java. Templates tipados, componentes, layouts e pré-compilação permitem construir aplicações HTML organizadas sem abandonar o ecossistema Spring.
Depois do Quick Start, o próximo passo é estruturar o código para que controllers forneçam View Models claros e templates cuidem apenas da apresentação.
Essa separação cria uma base simples para evoluir o sistema para páginas maiores, componentes reutilizáveis e interfaces dinâmicas.
- Documentação oficial do JTE
- Sintaxe dos templates JTE
- JTE Spring Boot Starter 4
- JTE Spring Boot Starter 3
- Pré-compilação de templates JTE
- Repositório oficial do JTE no GitHub
- Spring Initializr
Veja também: mais conteúdos sobre Java.
Spring Boot and Angular: Hands-on full-stack development with Java, Spring, Angular and TypeScript (English Edition)
Spring Boot and Angular: Hands-on full-stack development with Java, Spring, Angular and TypeScript (English Edition). Produto recomendado para estudos, programação e produtividade.



Deixe um comentário