Construindo um Localizador de Receitas: Integração com APIs REST e UX Responsiva

Arquitetura de Integração com a API TheMealDB

A integração de APIs de terceiros é um pilar fundamental no desenvolvimento web moderno para fornecer dados dinâmicos e ricos. A TheMealDB oferece uma interface RESTful gratuita que permite acessar um vasto catálogo de instruções culinárias. Para construir uma aplicação robusta, é necessário mapear os principais pontos de extremidade (endpoints) e estruturar a comunicação assíncrona.

Funcionalidade Endpoint Descrição
Busca por Termo /search.php?s={termo} Localiza receitas através de palavras-chave.
Consulta por ID /lookup.php?i={id} Retorna os detalhes completos de um prato específico.
Receita Aleatória /random.php Obtém uma sugestão culinária ao acaso.

Implementação de Requisições Assíncronas

Utilizamos a Fetch API do JavaScript para realizar as chamadas de rede. Abaixo, encapsulamos a lógica em funções modulares para facilitar a manutenção e o reaproveitamento de código.


// Função para localizar pratos por palavra-chave
async function filtrarReceitas(termoBusca) {
    try {
        const urlAlvo = `https://www.themealdb.com/api/json/v1/1/search.php?s=${encodeURIComponent(termoBusca)}`;
        const conexao = await fetch(urlAlvo);
        
        if (!conexao.ok) throw new Error('Falha na comunicação com o servidor');
        
        const resultado = await conexao.json();
        return resultado.meals || [];
    } catch (erro) {
        console.error('Erro na pesquisa:', erro);
        return [];
    }
}

// Função para buscar detalhes de um item específico
async function carregarDetalhesPrato(identificador) {
    const resposta = await fetch(`https://www.themealdb.com/api/json/v1/1/lookup.php?i=${identificador}`);
    const dados = await resposta.json();
    return dados.meals ? dados.meals[0] : null;
}

Processamento e Normalização de Dados

Os dados retornados pela TheMealDB possuem uma estrutura peculiar para ingredientes e medidas (espalhados em campos numerados de 1 a 20). É recomendável normalizar esses dados para um formato de array mais amigável ao frontend.


function formatarDadosReceita(dadosBrutos) {
    const listaIngredientes = [];
    
    for (let contador = 1; contador <= 20; contador++) {
        const ingrediente = dadosBrutos[`strIngredient${contador}`];
        const quantidade = dadosBrutos[`strMeasure${contador}`];
        
        if (ingrediente && ingrediente.trim() !== "") {
            listaIngredientes.push({
                item: ingrediente.trim(),
                medida: quantidade ? quantidade.trim() : 'a gosto'
            });
        }
    }
    
    return {
        id: dadosBrutos.idMeal,
        titulo: dadosBrutos.strMeal,
        categoria: dadosBrutos.strCategory,
        origem: dadosBrutos.strArea,
        passoAPasso: dadosBrutos.strInstructions,
        capa: dadosBrutos.strMealThumb,
        linkVideo: dadosBrutos.strYoutube,
        componentes: listaIngredientes
    };
}

Estratégia de Cache e Performance

Para evitar requisições redundantes e melhorar a velocidade de resposta, implementamos um sistema de cache em memória com tempo de expiração (TTL).


class GerenciadorDeCache {
    constructor(expiracaoEmMinutos = 15) {
        this.repositorio = new Map();
        this.tempoVida = expiracaoEmMinutos * 60 * 1000;
    }

    salvar(chave, conteudo) {
        this.repositorio.set(chave, {
            valor: conteudo,
            criadoEm: Date.now()
        });
    }

    obter(chave) {
        const item = this.repositorio.get(chave);
        if (!item) return null;

        const expirou = (Date.now() - item.criadoEm) > this.tempoVida;
        if (expirou) {
            this.repositorio.delete(chave);
            return null;
        }
        return item.valor;
    }
}

const cacheReceitas = new GerenciadorDeCache(30);

Interface de Usuário e Feedback Visual

O suceso da aplicação depende de como os resultados são apresentados. Implementamos uma grade responsiva e efeitos de interatividade para melhorar a experiência do usuário (UX).

Efeito de Hover nos Cards

Ao passar o mouse sobre uma receita, as informações principais são reveladas através de uma sobreposição suave com CSS.


.container-receitas {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 25px;
    padding: 20px;
}

.card-prato {
    position: relative;
    overflow: hidden;
    cursor: pointer;
    border: 3px solid #fff;
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

.card-prato-info {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.75);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 0.3s ease-out;
}

.card-prato:hover .card-prato-info {
    opacity: 1;
}

Adaptabilidade e Design Responsivo

A aplicação utiliza Media Queries para garantir que a navegação seja fluida tanto em desktops quanto em dispositivos móveis, ajustando o número de colunas da grade de acordo com a largura do viewport.


/* Ajustes para Tablets */
@media (max-width: 900px) {
    .container-receitas {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* Ajustes para Smartphones */
@media (max-width: 600px) {
    .container-receitas {
        grid-template-columns: 1fr;
    }
    
    .busca-input {
        width: 90%;
    }
    
    .detalhes-imagem {
        width: 100%;
        height: auto;
    }
}

Tratamento de Estados e Erros

Uma aplicação resiliente deve informar ao usuário o que está acontecendo durante o ciclo de vida da requisição.

  • Carregamento: Exibir um spinner ou mensagem enquanto os dados são buscados.
  • Sem Resultados: Mostrar um aviso amigável caso a busca não retorne nenhum prato.
  • Falha na Rede: Capturar exceções do fetch e sugerir que o usuário verifique sua conexão.
  • Validação de Input: Impedir envios de formulários vazios com alertas informativos.

Tags: javascript API-REST css-grid frontend web-development

Publicado em 8-4 11:01