DEV Community

Cover image for 🚀 Desenvolvimento Full-Stack Moderno: TanStack Router + .NET
Danilo O. Pinheiro, dopme.io
Danilo O. Pinheiro, dopme.io

Posted on

🚀 Desenvolvimento Full-Stack Moderno: TanStack Router + .NET

✨ 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     │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
└─────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. Frontend → Backend: Requisições HTTP via fetch ou bibliotecas como Axios
  2. Backend → Frontend: Respostas JSON tipadas
  3. 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();
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

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,
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

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 })
Enter fullscreen mode Exit fullscreen mode
// 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>
  )
}
Enter fullscreen mode Exit fullscreen mode

Por que essa abordagem?

  • File-Based Routing: Rotas definidas por arquivos, facilitando navegação e manutenção
  • Type Safety: Interface WeatherForecast garante 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
Enter fullscreen mode Exit fullscreen mode

✅ 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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
            }));
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

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.')
}
Enter fullscreen mode Exit fullscreen mode

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();
        });
    });
}
Enter fullscreen mode Exit fullscreen mode

📊 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:

  1. Experimente o projeto de exemplo e adapte para suas necessidades
  2. Explore recursos avançados do TanStack Router como data loading e route masking
  3. Implemente autenticação e autorização na API
  4. Adicione testes unitários e de integração
  5. 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)