✨ Construindo Aplicações Web Escaláveis com Type-Safe Routing e APIs RESTful
No cenário atual de desenvolvimento de software, a necessidade de criar aplicações web modernas, escaláveis e com excelente experiência do desenvolvedor é cada vez mais crítica. Este artigo apresenta uma abordagem prática para construir aplicações full-stack utilizando React com TanStack Router no frontend e ASP.NET Core no backend, demonstrando como essas tecnologias se complementam para criar soluções robustas e type-safe.
📖 Introdução
O desenvolvimento de aplicações web modernas exige uma combinação harmoniosa entre frontend e backend, onde cada camada deve ser construída com as melhores ferramentas disponíveis. Enquanto o ecossistema JavaScript/TypeScript domina o frontend com frameworks como React, o .NET continua sendo uma das plataformas mais robustas para construção de APIs e serviços backend.
Este artigo aborda a integração entre TanStack Router — uma solução moderna de roteamento type-safe para React — e ASP.NET Core, demonstrando como criar uma arquitetura full-stack que prioriza type safety, performance e manutenibilidade. Você aprenderá a configurar um projeto completo, desde a estrutura inicial até a implementação de rotas, APIs e integração entre as camadas.
🎯 Objetivo do Artigo
Ao final deste artigo, você será capaz de:
- Entender os conceitos fundamentais de roteamento type-safe com TanStack Router e construção de APIs RESTful com ASP.NET Core
- Conhecer boas práticas no ecossistema .NET moderno, incluindo configuração de CORS, documentação de APIs e estruturação de projetos
- Aplicar o conhecimento em cenários reais através de um projeto prático que demonstra a integração completa entre frontend e backend
- Evitar erros comuns de implementação, como problemas de CORS, configuração incorreta de rotas SPA e falta de type safety
🧠 Conceitos Fundamentais
TanStack Router: Roteamento Type-Safe
TanStack Router é uma biblioteca de roteamento para React que prioriza type safety e developer experience. Diferente de soluções tradicionais como React Router, o TanStack Router oferece:
- Type Safety Completo: As rotas são tipadas em tempo de compilação, prevenindo erros comuns de navegação
- Code Splitting Automático: Divisão automática de código para otimização de performance
- File-Based Routing: Sistema de rotas baseado em arquivos, similar ao Next.js, mas com mais flexibilidade
- Data Loading Integrado: Suporte nativo para carregamento de dados com estados de loading e erro
ASP.NET Core: Framework Moderno para APIs
ASP.NET Core é o framework web da Microsoft para construção de aplicações modernas:
- Multiplataforma: Roda em Windows, Linux e macOS
- Alto Desempenho: Um dos frameworks web mais rápidos disponíveis
- Minimal APIs: Sintaxe simplificada para criação de endpoints
- Integração Nativa: Suporte completo para JSON, CORS, autenticação e muito mais
Type Safety no Full-Stack
A type safety não deve ser limitada ao frontend. Ao usar TypeScript no React e C# no backend, garantimos:
- Contratos Claros: Interfaces e DTOs bem definidos entre frontend e backend
- Detecção Precoce de Erros: Problemas identificados em tempo de compilação
- Melhor Autocomplete: IDEs fornecem sugestões precisas em ambas as camadas
- Refatoração Segura: Mudanças podem ser feitas com confiança
🏗️ Arquitetura e Estrutura
Visão Geral da Arquitetura
┌─────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ TanStack │ │ Components │ │ State │ │
│ │ Router │ │ (React) │ │ Management │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ │ HTTP/REST │
└──────────────────────────┼──────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────────┐
│ Backend (.NET) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Controllers │ │ Services │ │ Data │ │
│ │ (API) │ │ (Business) │ │ Access │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
Organização de Camadas
Frontend (React + TanStack Router)
src/
├── routes/ # Rotas baseadas em arquivos
│ ├── __root.tsx # Rota raiz (layout principal)
│ ├── index.tsx # Página inicial (/)
│ ├── about.tsx # Página sobre (/about)
│ └── api-example.tsx # Exemplo de integração (/api-example)
├── components/ # Componentes reutilizáveis
├── hooks/ # Custom hooks
├── utils/ # Funções utilitárias
└── main.tsx # Ponto de entrada
Princípios de Organização:
-
File-Based Routing: Cada arquivo em
routes/representa uma rota - Co-location: Componentes relacionados ficam próximos às rotas
- Separação de Responsabilidades: Lógica de negócio separada de apresentação
Backend (ASP.NET Core)
NetTanstack.Api/
├── Controllers/ # Controladores da API
│ └── WeatherForecastController.cs
├── Services/ # Serviços de negócio
├── Models/ # Modelos e DTOs
├── Program.cs # Configuração e pipeline
└── appsettings.json # Configurações
Princípios de Organização:
- Controller-Based: Organização por recursos/funcionalidades
- Dependency Injection: Uso extensivo de DI para desacoplamento
- Minimal APIs: Endpoints simples podem usar Minimal APIs
Comunicação entre Componentes
-
Frontend → Backend: Requisições HTTP via
fetchou bibliotecas como Axios - Backend → Frontend: Respostas JSON tipadas
- Type Safety: Contratos definidos em TypeScript (frontend) e C# (backend)
Impactos em Escalabilidade e Manutenção
- Escalabilidade Horizontal: Backend pode ser escalado independentemente
- Deploy Independente: Frontend e backend podem ser deployados separadamente
- Manutenibilidade: Código organizado e type-safe facilita manutenção
- Testabilidade: Separação clara permite testes isolados
🛠️ Implementação no .NET
Estrutura de Projeto
1. Configuração do Backend (ASP.NET Core)
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Adicionar serviços
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Configurar CORS para permitir requisições do frontend
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowReactApp", policy =>
{
policy.WithOrigins("http://localhost:5173")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
// Configurar pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors("AllowReactApp");
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
// Exemplo de Minimal API
app.MapGet("/api/hello", () => new { message = "Olá do backend .NET!" })
.WithName("Hello");
app.Run();
Por que essas decisões?
- CORS Configurado: Permite comunicação entre frontend e backend em desenvolvimento
- Swagger em Desenvolvimento: Facilita testes e documentação da API
- Minimal APIs: Sintaxe mais limpa para endpoints simples
- Controllers: Melhor para endpoints mais complexos com múltiplas ações
2. Exemplo de Controller
// Controllers/WeatherForecastController.cs
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Congelante", "Frio", "Fresco", "Ameno", "Quente", "Tropical", "Escaldante"
};
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
public record WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
Boas Práticas Aplicadas:
- Records para DTOs: Sintaxe concisa e imutabilidade por padrão
-
Atributos de Roteamento:
[ApiController]e[Route]para convenções claras - Separação de Responsabilidades: Controller apenas coordena, lógica em serviços (em projetos maiores)
Configuração do Frontend (React + TanStack Router)
1. Configuração do Vite
// vite.config.ts
import { defineConfig, type Plugin } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
import type { IncomingMessage, ServerResponse } from 'node:http'
import type { NextFunction } from 'node:connect'
// Plugin para garantir fallback para index.html em todas as rotas (SPA)
const spaFallback = (): Plugin => {
return {
name: 'spa-fallback',
configureServer(server) {
return () => {
server.middlewares.use((req: IncomingMessage, res: ServerResponse, next: NextFunction) => {
if (
req.url &&
!req.url.startsWith('/api') &&
!req.url.includes('.') &&
req.url !== '/'
) {
req.url = '/index.html'
}
next()
})
}
},
}
}
export default defineConfig({
plugins: [
tanstackRouter({
target: 'react',
autoCodeSplitting: true,
}),
react(),
spaFallback(),
],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
},
},
},
})
Decisões Técnicas:
- Plugin SPA Fallback: Garante que recarregar qualquer rota funcione corretamente
-
Proxy para API: Redireciona
/api/*para o backend, evitando problemas de CORS em desenvolvimento - Auto Code Splitting: TanStack Router divide automaticamente o código por rota
2. Estrutura de Rotas
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
const RootLayout = () => (
<>
<nav className="main-nav">
<div className="nav-container">
<Link to="/" className="nav-link [&.active]:font-bold">
Home
</Link>
<Link to="/about" className="nav-link [&.active]:font-bold">
Sobre
</Link>
<Link to="/api-example" className="nav-link [&.active]:font-bold">
API Example
</Link>
</div>
</nav>
<main className="main-content">
<Outlet />
</main>
<TanStackRouterDevtools />
</>
)
export const Route = createRootRoute({ component: RootLayout })
// src/routes/api-example.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useState, useEffect } from 'react'
export const Route = createFileRoute('/api-example')({
component: ApiExample,
})
interface WeatherForecast {
date: string
temperatureC: number
temperatureF: number
summary: string
}
function ApiExample() {
const [data, setData] = useState<WeatherForecast[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
const response = await fetch('/api/weatherforecast')
if (!response.ok) {
throw new Error('Erro ao buscar dados')
}
const result = await response.json()
setData(result)
setError(null)
} catch (err) {
setError(err instanceof Error ? err.message : 'Erro desconhecido')
} finally {
setLoading(false)
}
}
fetchData()
}, [])
return (
<div className="page-container">
{/* UI do componente */}
</div>
)
}
Por que essa abordagem?
- File-Based Routing: Rotas definidas por arquivos, facilitando navegação e manutenção
-
Type Safety: Interface
WeatherForecastgarante que os dados da API sejam tipados - Error Handling: Tratamento adequado de erros e estados de loading
- Separation of Concerns: Lógica de fetch separada da apresentação
🔄 Boas Práticas e Cuidados
1. Evitar Acoplamento Excessivo
❌ Ruim:
// Frontend conhece detalhes de implementação do backend
const response = await fetch('http://localhost:5000/api/weatherforecast')
const data = await response.json()
// Sem validação de tipos
✅ Bom:
// Abstração através de serviços
import { weatherService } from '@/services/weather'
const data = await weatherService.getForecast()
// Tipos garantidos, fácil de mockar em testes
2. Garantir Testabilidade
Backend:
- Use Dependency Injection para todos os serviços
- Separe lógica de negócio dos controllers
- Crie interfaces para dependências externas
Frontend:
- Extraia lógica de fetch para hooks customizados
- Use serviços para comunicação com API
- Facilite mocking em testes
3. Pensar em Observabilidade
// Adicionar logging estruturado
builder.Services.AddLogging(builder =>
{
builder.AddConsole();
builder.AddSeq(); // Ou outro sistema de logging
});
// Logar requisições importantes
_logger.LogInformation("Weather forecast requested at {Time}", DateTime.UtcNow);
4. Tratar Falhas e Exceções Corretamente
Backend:
// Middleware global de tratamento de exceções
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var exception = context.Features.Get<IExceptionHandlerFeature>();
if (exception != null)
{
await context.Response.WriteAsync(JsonSerializer.Serialize(new
{
error = "An error occurred",
message = exception.Error.Message
}));
}
});
});
Frontend:
// Tratamento consistente de erros
try {
const data = await fetch('/api/weatherforecast')
if (!data.ok) {
throw new Error(`HTTP error! status: ${data.status}`)
}
// processar dados
} catch (error) {
// Logar erro e mostrar mensagem amigável ao usuário
console.error('Failed to fetch weather data:', error)
setError('Não foi possível carregar os dados. Tente novamente.')
}
5. Considerar Segurança desde o Início
- CORS: Configure apenas origens necessárias em produção
- Validação de Input: Sempre valide dados recebidos
- HTTPS: Force HTTPS em produção
- Rate Limiting: Implemente limites de requisição
- Autenticação: Use JWT ou OAuth2 para APIs protegidas
// CORS mais restritivo em produção
if (app.Environment.IsProduction())
{
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowReactApp", policy =>
{
policy.WithOrigins("https://meudominio.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
📊 Benefícios da Abordagem
Código Mais Limpo e Organizado
- Type Safety: Erros detectados em tempo de compilação
- Estrutura Clara: File-based routing facilita navegação
- Convenções: Padrões consistentes reduzem decisões arbitrárias
Facilidade de Manutenção
- Separação de Responsabilidades: Frontend e backend independentes
- Refatoração Segura: TypeScript e C# permitem refatorações com confiança
- Documentação: Swagger gera documentação automática da API
Escalabilidade
- Deploy Independente: Escalar frontend e backend separadamente
- Performance: Code splitting automático otimiza carregamento
- Arquitetura Flexível: Fácil adicionar novos serviços ou features
Melhor Experiência para Times de Desenvolvimento
- Developer Experience: Autocomplete preciso, erros claros
- Hot Reload: Mudanças refletidas instantaneamente
- DevTools: TanStack Router DevTools facilitam debug
Menor Risco em Produção
- Type Safety: Previne erros de runtime
- Testabilidade: Código testável por design
- Observabilidade: Logging e monitoramento facilitados
🚧 Desafios Comuns
1. Complexidade Desnecessária
Problema: Adicionar muitas camadas e abstrações sem necessidade real.
Solução: Comece simples. Adicione complexidade apenas quando necessário. Um controller direto pode ser suficiente para APIs simples.
2. Uso Incorreto de Ferramentas
Problema: Usar TanStack Router para aplicações muito simples onde React Router seria suficiente.
Solução: Avalie as necessidades do projeto. TanStack Router brilha em aplicações complexas com muitas rotas e necessidade de type safety.
3. Falta de Padronização
Problema: Diferentes padrões de nomenclatura, estrutura de pastas e estilos de código.
Solução: Estabeleça convenções desde o início e documente-as. Use ferramentas como ESLint e EditorConfig para manter consistência.
4. Dificuldade de Evolução sem Refatoração
Problema: Código acoplado dificulta adicionar novas features.
Solução:
- Use Dependency Injection
- Separe lógica de negócio de apresentação
- Crie interfaces para dependências
- Escreva testes que facilitem refatoração
5. Problemas de CORS em Produção
Problema: Configuração de CORS muito permissiva ou incorreta.
Solução: Configure CORS específico para cada ambiente. Em produção, liste apenas origens necessárias.
✅ Conclusão
Este artigo apresentou uma abordagem prática para construir aplicações full-stack modernas utilizando React com TanStack Router no frontend e ASP.NET Core no backend. Através da combinação dessas tecnologias, conseguimos criar soluções que priorizam type safety, performance e manutenibilidade.
Os principais pontos abordados foram:
- Conceitos fundamentais de roteamento type-safe e construção de APIs RESTful
- Arquitetura moderna com separação clara de responsabilidades
- Implementação prática com exemplos de código reais
- Boas práticas para garantir qualidade e sustentabilidade
- Desafios comuns e como evitá-los
A escolha de tecnologias modernas como TanStack Router e ASP.NET Core, combinada com boas práticas de arquitetura, resulta em aplicações mais robustas, escaláveis e fáceis de manter. A type safety em ambas as camadas (TypeScript e C#) reduz significativamente erros em runtime e melhora a experiência de desenvolvimento.
É importante lembrar que boas decisões arquiteturais e técnicas impactam diretamente a qualidade, a escalabilidade e a longevidade das aplicações. Investir tempo em estruturação adequada e escolha de ferramentas apropriadas paga dividendos a longo prazo.
Próximos Passos:
- Experimente o projeto de exemplo e adapte para suas necessidades
- Explore recursos avançados do TanStack Router como data loading e route masking
- Implemente autenticação e autorização na API
- Adicione testes unitários e de integração
- Configure CI/CD para deploy automatizado
Aplique esses conceitos de forma gradual e consciente, sempre avaliando as necessidades específicas do seu projeto. Comece simples e evolua conforme a complexidade aumenta.
🤝 Conecte-se Comigo
Se você trabalha com .NET moderno e quer dominar arquitetura, C#, DevOps ou interoperabilidade, vamos conversar:
💼 LinkedIn
💻 Dev.to
✍️ Medium
✍️ Substack
📬 Email
Referências e Recursos:
Top comments (0)