8. Integração com Backends
Este documento detalha as melhores práticas para integrar soluções web Astro com diferentes backends e serviços de API, garantindo uma arquitetura robusta e eficiente.
Visão Geral
A integração eficiente com backends é essencial para criar aplicações web dinâmicas e funcionais. O Astro 5 oferece várias abordagens para conectar-se a backends, desde APIs RESTful até GraphQL e serviços serverless.
Estratégias de Integração
Server-Side Rendering (SSR)
Com o modo SSR do Astro, você pode buscar dados diretamente no servidor antes de enviar HTML ao cliente:
---export const prerender = false; // Habilita SSR para esta rota
// Função para buscar dados do produtoasync function fetchProduct(id) { const response = await fetch(`https://api.exemplo.com/produtos/${id}`);
if (!response.ok) { return { notFound: true }; }
return await response.json();}
// Obtém o ID do produto da URLconst { id } = Astro.params;const product = await fetchProduct(id);
// Redireciona para 404 se o produto não for encontradoif (product.notFound) { return Astro.redirect('/404');}---
<html> <head> <title>{product.name}</title> </head> <body> <h1>{product.name}</h1> <p>{product.description}</p> <p>Preço: R$ {product.price}</p> </body></html>API Endpoints
Crie endpoints de API diretamente no Astro para servir como middleware entre o cliente e seu backend:
export async function GET({ request }) { try { // Obter parâmetros da query const url = new URL(request.url); const categoria = url.searchParams.get('categoria');
// Construir URL da API let apiUrl = 'https://api.exemplo.com/produtos'; if (categoria) { apiUrl += `?categoria=${categoria}`; }
// Buscar dados const response = await fetch(apiUrl); const data = await response.json();
// Retornar resposta formatada return new Response( JSON.stringify({ success: true, data, timestamp: new Date().toISOString(), }), { status: 200, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=60, stale-while-revalidate=300', }, } ); } catch (error) { console.error('Erro ao buscar produtos:', error);
return new Response( JSON.stringify({ success: false, message: 'Erro ao buscar produtos', error: error.message, }), { status: 500, headers: { 'Content-Type': 'application/json', }, } ); }}
export async function POST({ request }) { try { // Obter dados do corpo da requisição const body = await request.json();
// Validar dados if (!body.nome || !body.preco) { return new Response( JSON.stringify({ success: false, message: 'Nome e preço são obrigatórios', }), { status: 400, headers: { 'Content-Type': 'application/json', }, } ); }
// Enviar dados para a API const response = await fetch('https://api.exemplo.com/produtos', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body), });
const data = await response.json();
// Retornar resposta return new Response( JSON.stringify({ success: true, data, message: 'Produto criado com sucesso', }), { status: 201, headers: { 'Content-Type': 'application/json', }, } ); } catch (error) { console.error('Erro ao criar produto:', error);
return new Response( JSON.stringify({ success: false, message: 'Erro ao criar produto', error: error.message, }), { status: 500, headers: { 'Content-Type': 'application/json', }, } ); }}Integrações Específicas
Supabase
O Supabase é uma alternativa open-source ao Firebase que oferece banco de dados PostgreSQL, autenticação, armazenamento e funções serverless.
Configuração Inicial
# Instalar o cliente Supabasenpm install @supabase/supabase-jsCliente Supabase
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL;const supabaseAnonKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);Autenticação com Supabase
---import BaseLayout from '../layouts/BaseLayout.astro';import { supabase } from '../lib/supabase';
// Verifica se o usuário já está autenticadoconst { data: { session } } = await supabase.auth.getSession();if (session) { return Astro.redirect('/dashboard');}---
<BaseLayout title="Login"> <div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md"> <h1 class="text-2xl font-bold mb-6">Login</h1>
<form id="login-form" class="space-y-4"> <div> <label for="email" class="block text-sm font-medium text-gray-700">Email</label> <input type="email" id="email" name="email" required class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500" /> </div>
<div> <label for="password" class="block text-sm font-medium text-gray-700">Senha</label> <input type="password" id="password" name="password" required class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500" /> </div>
<div> <button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500" > Entrar </button> </div>
<div id="error-message" class="text-red-500 text-sm hidden"></div> </form> </div></BaseLayout>
<script> import { supabase } from '../lib/supabase';
const form = document.getElementById('login-form'); const errorMessage = document.getElementById('error-message');
form.addEventListener('submit', async (e) => { e.preventDefault();
const formData = new FormData(form); const email = formData.get('email'); const password = formData.get('password');
try { errorMessage.classList.add('hidden');
const { data, error } = await supabase.auth.signInWithPassword({ email, password, });
if (error) { errorMessage.textContent = error.message; errorMessage.classList.remove('hidden'); return; }
// Redireciona para o dashboard após login bem-sucedido window.location.href = '/dashboard'; } catch (error) { console.error('Erro ao fazer login:', error); errorMessage.textContent = 'Ocorreu um erro ao fazer login. Tente novamente.'; errorMessage.classList.remove('hidden'); } });</script>Consultas ao Banco de Dados
---import BaseLayout from '../layouts/BaseLayout.astro';import ProductCard from '../components/ProductCard.astro';import { supabase } from '../lib/supabase';
// Buscar produtos do Supabaseconst { data: produtos, error } = await supabase .from('produtos') .select('*') .order('created_at', { ascending: false });
if (error) { console.error('Erro ao buscar produtos:', error);}---
<BaseLayout title="Produtos"> <div class="container mx-auto py-8"> <h1 class="text-3xl font-bold mb-8">Nossos Produtos</h1>
{error && ( <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6"> Erro ao carregar produtos. Por favor, tente novamente mais tarde. </div> )}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {produtos?.map((produto) => ( <ProductCard id={produto.id} name={produto.nome} price={produto.preco} description={produto.descricao} image={produto.imagem_url} /> ))} </div> </div></BaseLayout>Cloudflare
O Astro tem suporte nativo para implantação no Cloudflare Pages, permitindo o uso de Cloudflare Workers para funções serverless.
Configuração para Cloudflare
import { defineConfig } from 'astro/config';import cloudflare from '@astrojs/cloudflare';
export default defineConfig({ output: 'server', adapter: cloudflare({ mode: 'directory', functionPerRoute: true, }),});Funções Cloudflare Workers
export async function GET({ request, env }) { // Acessa o KV namespace const KV = env.CONTADOR_KV;
// Obtém o valor atual do contador let contador = await KV.get('visitas'); contador = contador ? parseInt(contador) : 0;
// Incrementa o contador contador++;
// Salva o novo valor await KV.put('visitas', contador.toString());
// Retorna o valor atual return new Response( JSON.stringify({ contador, timestamp: new Date().toISOString(), }), { headers: { 'Content-Type': 'application/json', }, } );}Padrões de Integração
Camada de Serviços
Organize suas integrações com backends em uma camada de serviços:
const API_BASE_URL = import.meta.env.PUBLIC_API_URL || 'https://api.exemplo.com';
/** * Cliente de API genérico com métodos para operações CRUD */export const apiClient = { /** * Realiza uma requisição GET * @param {string} endpoint - Endpoint da API * @param {Object} params - Parâmetros de query * @returns {Promise<any>} Dados da resposta */ async get(endpoint, params = {}) { const queryParams = new URLSearchParams(params).toString(); const url = `${API_BASE_URL}${endpoint}${queryParams ? `?${queryParams}` : ''}`;
const response = await fetch(url, { headers: { 'Content-Type': 'application/json', // Adicione headers de autenticação se necessário }, });
if (!response.ok) { throw new Error(`Erro na API: ${response.status} ${response.statusText}`); }
return await response.json(); },
/** * Realiza uma requisição POST * @param {string} endpoint - Endpoint da API * @param {Object} data - Dados a serem enviados * @returns {Promise<any>} Dados da resposta */ async post(endpoint, data) { const url = `${API_BASE_URL}${endpoint}`;
const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', // Adicione headers de autenticação se necessário }, body: JSON.stringify(data), });
if (!response.ok) { throw new Error(`Erro na API: ${response.status} ${response.statusText}`); }
return await response.json(); },
/** * Realiza uma requisição PUT * @param {string} endpoint - Endpoint da API * @param {Object} data - Dados a serem enviados * @returns {Promise<any>} Dados da resposta */ async put(endpoint, data) { const url = `${API_BASE_URL}${endpoint}`;
const response = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json', // Adicione headers de autenticação se necessário }, body: JSON.stringify(data), });
if (!response.ok) { throw new Error(`Erro na API: ${response.status} ${response.statusText}`); }
return await response.json(); },
/** * Realiza uma requisição DELETE * @param {string} endpoint - Endpoint da API * @returns {Promise<any>} Dados da resposta */ async delete(endpoint) { const url = `${API_BASE_URL}${endpoint}`;
const response = await fetch(url, { method: 'DELETE', headers: { 'Content-Type': 'application/json', // Adicione headers de autenticação se necessário }, });
if (!response.ok) { throw new Error(`Erro na API: ${response.status} ${response.statusText}`); }
return await response.json(); },};Serviços Específicos
import { apiClient } from './api';
export const produtosService = { /** * Busca todos os produtos * @param {Object} params - Parâmetros de filtro * @returns {Promise<Array>} Lista de produtos */ async listarTodos(params = {}) { return await apiClient.get('/produtos', params); },
/** * Busca um produto pelo ID * @param {string|number} id - ID do produto * @returns {Promise<Object>} Dados do produto */ async buscarPorId(id) { return await apiClient.get(`/produtos/${id}`); },
/** * Cria um novo produto * @param {Object} produto - Dados do produto * @returns {Promise<Object>} Produto criado */ async criar(produto) { return await apiClient.post('/produtos', produto); },
/** * Atualiza um produto existente * @param {string|number} id - ID do produto * @param {Object} produto - Dados atualizados * @returns {Promise<Object>} Produto atualizado */ async atualizar(id, produto) { return await apiClient.put(`/produtos/${id}`, produto); },
/** * Remove um produto * @param {string|number} id - ID do produto * @returns {Promise<Object>} Resposta da API */ async remover(id) { return await apiClient.delete(`/produtos/${id}`); },};Conclusão
A integração eficiente com backends é essencial para criar aplicações web dinâmicas e funcionais. O Astro 5 oferece várias abordagens para conectar-se a backends, desde APIs RESTful até GraphQL e serviços serverless.
Ao seguir os padrões e práticas descritos neste documento, você pode criar integrações robustas e eficientes que aproveitam ao máximo os recursos do Astro e dos serviços de backend escolhidos.
Os próximos documentos detalharão outros aspectos do desenvolvimento:
- Ferramentas de Desenvolvimento: Setup do ambiente de desenvolvimento
- Padrões de JavaScript: Organização de funções e módulos
- Testes e Qualidade: Estratégias de teste e garantia de qualidade
Última atualização: 21 de março de 2025