Pular para o conteúdo

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:

src/pages/produtos/[id].astro
---
export const prerender = false; // Habilita SSR para esta rota
// Função para buscar dados do produto
async 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 URL
const { id } = Astro.params;
const product = await fetchProduct(id);
// Redireciona para 404 se o produto não for encontrado
if (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:

src/pages/api/produtos.js
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

Terminal window
# Instalar o cliente Supabase
npm install @supabase/supabase-js

Cliente Supabase

src/lib/supabase.js
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

src/pages/login.astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { supabase } from '../lib/supabase';
// Verifica se o usuário já está autenticado
const { 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

src/pages/produtos.astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
import ProductCard from '../components/ProductCard.astro';
import { supabase } from '../lib/supabase';
// Buscar produtos do Supabase
const { 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

astro.config.mjs
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

src/pages/api/contador.js
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:

src/services/api.js
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

src/services/produtos.js
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:


Última atualização: 21 de março de 2025