Skip to content

Latest commit

 

History

History
319 lines (220 loc) · 10.1 KB

File metadata and controls

319 lines (220 loc) · 10.1 KB

Integração Frontend ↔ Backend

Neste workshop vamos implementar juntos a integração real entre o frontend React e o backend Spring Boot.


O que já está pronto

Antes de codar, é importante entender o que já existe no projeto:

1. Instância do axios (src/lib/axios.ts)

import axios from 'axios';

export const api = axios.create({
    baseURL: import.meta.env.VITE_API_URL ?? 'http://localhost:8080',
});

O axios é uma biblioteca para fazer requisições HTTP. Em vez de usarmos fetch diretamente, criamos uma instância api já configurada com a URL base do backend. Assim, todas as chamadas partem de http://localhost:8080.

2. Arquivo de funções da API (src/lib/api.ts)

Este arquivo centraliza todas as chamadas ao backend. Cada função representa uma rota da API.

3. TanStack Query (useQuery e useMutation)

O TanStack Query gerencia o estado assíncrono das requisições:

  • useQuery — para requisições de leitura (GET). Chama a função automaticamente, guarda o resultado e expõe isLoading.
  • useMutation — para requisições de escrita (POST, PUT, DELETE). Chama a função sob demanda e expõe isPending.

O que vamos implementar

Abra o arquivo src/lib/api.ts. Há três funções com TODO que precisam ser implementadas:

// TODO 1 — buscar todos os lugares
export async function fetchPlaces(): Promise<Place[]> { ... }

// TODO 2 — buscar um lugar pelo ID
export async function fetchPlace(id: string): Promise<Place> { ... }

// TODO 3 — cadastrar um novo lugar
export async function createPlace(data: PlaceFormData, image: File | null): Promise<Place> { ... }

TODO 1 — fetchPlaces: listar todos os lugares

Rota do backend

GET http://localhost:8080/places

Resposta esperada: array de objetos Place.

Implementação

export async function fetchPlaces(): Promise<Place[]> {
    const { data } = await api.get<Place[]>('/places');
    return data;
}
Trecho Explicação
api.get('/places') Faz uma requisição GET para http://localhost:8080/places
<Place[]> Informa ao TypeScript o tipo da resposta esperada
const { data } Desestrutura o objeto de resposta do axios — data contém o corpo da resposta
return data Retorna o array de lugares para quem chamar a função

Como está sendo usada

Em src/pages/Home/index.tsx:

const { data: places = [], isLoading } = useQuery({
    queryKey: ['places'],
    queryFn: fetchPlaces,
});

Após implementar, volte ao navegador — os cards aparecerão automaticamente na tela inicial.


TODO 2 — fetchPlace: buscar um lugar pelo ID

Rota do backend

GET http://localhost:8080/places/{id}

Resposta esperada: objeto único Place.

Implementação

export async function fetchPlace(id: string): Promise<Place> {
    const { data } = await api.get<Place>(`/places/${id}`);
    return data;
}
Trecho Explicação
`/places/${id}` Template string que monta a URL com o ID dinâmico, ex: /places/123
api.get<Place>(...) GET que retorna um único objeto no lugar de um array
const { data } / return Mesmo padrão do TODO 1

Como está sendo usada

Em src/pages/PlaceDetail/index.tsx e src/pages/EditPlace/index.tsx:

const { data: place } = useQuery({
    queryKey: ['places', id],
    queryFn: () => fetchPlace(id!),
    enabled: !!id,
});

Após implementar, as páginas de detalhe e edição passarão a carregar os dados reais.


TODO 3 — createPlace: cadastrar um novo lugar

Rota do backend

POST http://localhost:8080/places
Content-Type: multipart/form-data

Este endpoint recebe os dados do lugar e, opcionalmente, um arquivo de imagem. Por isso usamos FormData em vez de JSON.

O que é FormData?

FormData é um formato nativo do navegador para enviar dados em pares chave-valor, incluindo arquivos. A função buildFormData (já implementada no topo do arquivo) monta esse objeto a partir dos campos do formulário.

Implementação

export async function createPlace(data: PlaceFormData, image: File | null): Promise<Place> {
    const formData = buildFormData(data, image);
    const { data: place } = await api.post<Place>('/places', formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
    });
    return place;
}
Trecho Explicação
buildFormData(data, image) Converte os dados do formulário em um FormData com os campos e a imagem (se houver)
api.post('/places', formData, ...) Faz uma requisição POST enviando o FormData como corpo
headers: { 'Content-Type': ... } Informa ao servidor que o corpo é multipart/form-data (necessário para envio de arquivos)
const { data: place } Renomeia data para place para evitar conflito com o parâmetro data da função
return place Retorna o objeto do lugar recém-criado

Como está sendo usada

Em src/components/PlaceForm/index.tsx:

const mutation = useMutation({
    mutationFn: () => createPlace(formData, imageFile),
    onSuccess: () => {
        toast.success('Lugar cadastrado com sucesso!');
        navigate('/');
    },
});

Após implementar, o formulário de cadastro enviará os dados para o backend e redirecionará para a home ao concluir.


Resumo dos padrões

Leitura (GET)         →  api.get<Tipo>(url)
Leitura por ID (GET)  →  api.get<Tipo>(`/rota/${id}`)
Criação (POST)        →  api.post<Tipo>(url, corpo, { headers })

Com esses três padrões dominados, as demais funções do arquivo (updatePlace, deletePlace, toggleFavorite) seguem a mesma lógica — só mudam o método HTTP e a URL.


O que já está pronto

Antes de codar, é importante entender o que já existe no projeto:

1. Instância do axios (src/lib/axios.ts)

import axios from 'axios';

export const api = axios.create({
    baseURL: import.meta.env.VITE_API_URL ?? 'http://localhost:8080',
});

O axios é uma biblioteca para fazer requisições HTTP. Em vez de usarmos fetch diretamente, criamos uma instância api já configurada com a URL base do backend. Assim, todas as chamadas partem de http://localhost:8080.

2. Arquivo de funções da API (src/lib/api.ts)

Este arquivo centraliza todas as chamadas ao backend. Cada função representa uma rota da API.

3. TanStack Query na página Home (src/pages/Home/index.tsx)

const { data: places = [], isLoading } = useQuery({
    queryKey: ['places'],
    queryFn: fetchPlaces,
});

O TanStack Query (useQuery) gerencia o estado assíncrono: ele chama fetchPlaces, guarda o resultado em data, e expõe isLoading enquanto aguarda a resposta.


O que vamos implementar

Abra o arquivo src/lib/api.ts e localize o TODO:

// TODO: implementar a busca de todos os lugares no backend
export async function fetchPlaces(): Promise<Place[]> {
    return [];
}

Atualmente a função retorna um array vazio. Nosso objetivo é fazer ela buscar os lugares reais do backend.


Passo a passo

Passo 1 — Entender a rota do backend

O backend expõe a seguinte rota:

GET http://localhost:8080/places

Resposta esperada (array de objetos Place):

[
  {
    "id": "...",
    "name": "Café Sereno",
    "category": "COFFEE_SHOP",
    "city": "São Paulo",
    "rating": 4.5,
    ...
  }
]

Você pode testar essa rota diretamente no navegador ou em ferramentas como Postman / Insomnia.

Passo 2 — Fazer a requisição GET com axios

Dentro de fetchPlaces, use a instância api para fazer um GET:

export async function fetchPlaces(): Promise<Place[]> {
    const { data } = await api.get<Place[]>('/places');
    return data;
}

O que está acontecendo aqui:

Trecho Explicação
api.get('/places') Faz uma requisição GET para http://localhost:8080/places
<Place[]> Informa ao TypeScript o tipo da resposta esperada
const { data } Desestrutura o objeto de resposta do axios — data contém o corpo da resposta
return data Retorna o array de lugares para quem chamar a função

Passo 3 — Verificar o resultado

Com o backend rodando, volte ao navegador e acesse a página inicial. Os cards dos lugares devem aparecer automaticamente — sem nenhuma outra alteração no código, pois o useQuery já está configurado para chamar fetchPlaces e exibir o resultado.


Resumo

fetchPlaces()             → faz GET /places
    ↓
api.get<Place[]>(...)     → axios envia a requisição HTTP
    ↓
{ data }                  → axios retorna { data, status, headers, ... }
    ↓
return data               → retorna o array de Place[]
    ↓
useQuery                  → recebe o array e atualiza a tela

Próximos passos

Agora que você entendeu o padrão, as demais funções no arquivo api.ts seguem a mesma lógica — só mudam o método HTTP (post, put, patch, delete) e a URL. Explore!