Consumindo uma API REST pública do zero
1. Fundamentos: O que é uma API REST e por que consumi-la?
API REST (Representational State Transfer) é um conjunto de regras que permite que sistemas se comuniquem via HTTP. Os conceitos fundamentais incluem:
- Endpoints: URLs que representam recursos específicos (ex:
/users,/posts) - Recursos: entidades manipuladas (usuários, posts, produtos)
- Métodos HTTP: GET (ler), POST (criar), PUT (atualizar), DELETE (remover)
APIs públicas como JSONPlaceholder, OpenWeather e GitHub oferecem dados gratuitos para aprendizado e prototipagem. O fluxo básico é: o frontend faz uma requisição HTTP para a API, que processa e retorna dados em JSON, que são então renderizados na interface.
2. Configurando o ambiente de desenvolvimento
Primeiro, crie um projeto Node.js e instale as dependências necessárias:
mkdir api-consumer
cd api-consumer
npm init -y
npm install react react-dom vite @vitejs/plugin-react
Estrutura de pastas recomendada:
/src
/services
apiService.js
/components
UserList.jsx
ErrorMessage.jsx
LoadingSpinner.jsx
App.jsx
main.jsx
Configure o Vite com o arquivo vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()]
});
3. Primeira requisição com Fetch API (puro JavaScript)
A Fetch API nativa do JavaScript é a forma mais simples de consumir APIs. Exemplo básico:
// Exemplo com async/await (recomendado)
async function getUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Erro na requisição:', error);
throw error;
}
}
// Uso
getUsers()
.then(users => console.log(users))
.catch(error => console.error(error));
O tratamento de erros é crucial: verifique response.ok e use try/catch para capturar exceções de rede.
4. Organizando o código em serviços reutilizáveis
Crie um módulo apiService.js que centraliza todas as requisições:
// services/apiService.js
const BASE_URL = 'https://jsonplaceholder.typicode.com';
const TIMEOUT = 10000; // 10 segundos
const defaultHeaders = {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer token_aqui' // para APIs autenticadas
};
async function request(endpoint, options = {}) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
try {
const response = await fetch(`${BASE_URL}${endpoint}`, {
...options,
headers: {
...defaultHeaders,
...options.headers
},
signal: controller.signal
});
if (!response.ok) {
const errorData = await response.json().catch(() => null);
throw new Error(
errorData?.message || `Erro ${response.status}: ${response.statusText}`
);
}
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
export const apiService = {
get: (endpoint, options = {}) => request(endpoint, { ...options, method: 'GET' }),
post: (endpoint, data, options = {}) =>
request(endpoint, { ...options, method: 'POST', body: JSON.stringify(data) }),
put: (endpoint, data, options = {}) =>
request(endpoint, { ...options, method: 'PUT', body: JSON.stringify(data) }),
delete: (endpoint, options = {}) => request(endpoint, { ...options, method: 'DELETE' })
};
5. Consumindo a API no React: hooks e estado
Agora, integre o serviço com componentes React usando hooks:
// components/UserList.jsx
import React, { useState, useEffect } from 'react';
import { apiService } from '../services/apiService';
import LoadingSpinner from './LoadingSpinner';
import ErrorMessage from './ErrorMessage';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
setError(null);
const data = await apiService.get('/users');
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error} />;
return (
<div className="user-list">
<h2>Lista de Usuários</h2>
{users.map(user => (
<div key={user.id} className="user-card">
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<p>Cidade: {user.address.city}</p>
</div>
))}
</div>
);
}
export default UserList;
6. Lidando com requisições assíncronas complexas
Para evitar problemas comuns como race conditions e requisições duplicadas:
// components/SearchableUserList.jsx
import React, { useState, useEffect, useRef } from 'react';
import { apiService } from '../services/apiService';
function SearchableUserList() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
const abortControllerRef = useRef(null);
const cacheRef = useRef(new Map());
useEffect(() => {
if (!query.trim()) {
setResults([]);
return;
}
// Verificar cache
if (cacheRef.current.has(query)) {
setResults(cacheRef.current.get(query));
return;
}
// Cancelar requisição anterior
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
const controller = new AbortController();
abortControllerRef.current = controller;
const fetchResults = async () => {
setLoading(true);
try {
const data = await apiService.get(`/users?q=${query}`, {
signal: controller.signal
});
// Armazenar em cache
cacheRef.current.set(query, data);
setResults(data);
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Erro na busca:', err);
}
} finally {
setLoading(false);
}
};
fetchResults();
return () => {
controller.abort(); // Cleanup: aborta se componente desmontar
};
}, [query]);
return (
<div>
<input
type="text"
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Buscar usuários..."
/>
{loading && <p>Buscando...</p>}
{results.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}
7. Tratamento de erros e feedback ao usuário
Implemente componentes reutilizáveis para feedback visual:
// components/ErrorMessage.jsx
import React from 'react';
function ErrorMessage({ message, onRetry }) {
return (
<div className="error-message">
<p>❌ Erro: {message}</p>
{onRetry && (
<button onClick={onRetry}>Tentar novamente</button>
)}
</div>
);
}
// components/LoadingSpinner.jsx
function LoadingSpinner() {
return (
<div className="loading-spinner">
<div className="spinner"></div>
<p>Carregando...</p>
</div>
);
}
Para retry automático com exponential backoff:
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
8. Boas práticas e próximos passos
Custom hooks para reutilização:
// hooks/useApi.js
import { useState, useEffect, useCallback } from 'react';
import { apiService } from '../services/apiService';
export function useApi(endpoint, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
const result = await apiService.get(endpoint, options);
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [endpoint]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
Testando com Postman/Insomnia: Antes de codificar, teste os endpoints com ferramentas como Postman para entender a estrutura dos dados.
Considerações importantes:
- Autenticação: APIs privadas exigem tokens (JWT, API keys) no header Authorization
- CORS: Configure o backend para permitir requisições de origens específicas
- Documentação: Consulte sempre a documentação oficial da API para entender endpoints, parâmetros e limites de taxa
Com essas bases, você está pronto para consumir qualquer API REST pública e construir aplicações React robustas e bem estruturadas.
Referências
- MDN Web Docs: Using the Fetch API — Guia completo sobre a Fetch API nativa do JavaScript
- React Documentation: useEffect — Documentação oficial sobre o hook useEffect para efeitos colaterais
- JSONPlaceholder — API REST pública gratuita para testes e prototipagem
- Axios GitHub Repository — Biblioteca popular para requisições HTTP com suporte a interceptors e cancelamento
- Vite Documentation — Guia oficial do Vite para configuração de projetos React modernos
- Postman Learning Center — Tutorial completo sobre como testar APIs com Postman
- REST API Tutorial — Guia conceitual sobre princípios e boas práticas REST