Construindo um Chatbot

Backend da Conversa

Endpoint REST, montagem de messages, chamada ao LLM e retorno JSON para o frontend.

Intermediário 30 min 30 pontos Leitura 0%

Nesta aula você vai

  • Criar POST /api/chat com validação de entrada
  • Montar array messages com system prompt
  • Retornar JSON padronizado ao cliente

Backend da Conversa

Objetivos

  • Fechar fluxo completo cliente → servidor → LLM → cliente
  • Endpoint production-ready mínimo

O maestro da conversa

O frontend é a vitrine. O backend é quem decide o que enviar ao modelo, protege a chave, valida entrada e formata saída.

Pense no endpoint /api/chat como recepcionista de hotel: ouve o hóspede (usuário), consulta o manual (system prompt), fala com o especialista (LLM) e devolve resposta clara — sem deixar o hóspede entrar na cozinha (API key).

Express (Node.js)

import express from 'express';
import { chat } from './llmClient.js';

const app = express();
app.use(express.json({ limit: '16kb' }));

const SYSTEM = `Você é assistente da Minha Loja.
Responda em português, tom profissional, máximo 150 palavras.`;

app.post('/api/chat', async (req, res) => {
  const { message } = req.body;

  if (!message || typeof message !== 'string' || message.length > 2000) {
    return res.status(400).json({ error: 'Mensagem inválida.' });
  }

  try {
    const reply = await chat(
      [
        { role: 'system', content: SYSTEM },
        { role: 'user', content: message },
      ],
      { temperature: 0.4, max_tokens: 300 },
    );

    res.json({ reply });
  } catch (err) {
    console.error(err);
    res.status(503).json({ error: 'Serviço indisponível.' });
  }
});

app.listen(3000);

Note as camadas: validação → montagem de messages → chamada encapsulada → resposta JSON ou erro amigável.

PHP (endpoint slim)

<?php
header('Content-Type: application/json');
$body = json_decode(file_get_contents('php://input'), true);
$message = trim($body['message'] ?? '');

if ($message === '' || strlen($message) > 2000) {
    http_response_code(400);
    echo json_encode(['error' => 'Mensagem inválida.']);
    exit;
}

// chame wrapper LLM...
echo json_encode(['reply' => $resposta]);

Mesma responsabilidade, outra stack. O contrato com o frontend permanece idêntico.

Contrato de API

Documente para o frontend — evita "funciona no Postman, quebra no React":

POST /api/chat
Body: { "message": string }
200: { "reply": string }
400: { "error": string }
503: { "error": string }

Conversa dev frontend ↔ dev backend:

Frontend: "Posso mandar messages[] completo do histórico?"
Backend: "Não. Manda só message. Eu carrego histórico do banco por sessão."
Frontend: "Então só preciso do cookie de sessão."
Backend: "Isso."

Session ID (preview matéria 5)

// Gere no primeiro acesso, guarde em cookie httpOnly
const sessionId = req.cookies.chat_session ?? crypto.randomUUID();
res.cookie('chat_session', sessionId, { httpOnly: true, sameSite: 'lax' });
// Próxima aula: carregar histórico por sessionId

Por enquanto cada mensagem é isolada. Na matéria de memória, esse sessionId vira a chave que liga as falas do usuário.

CORS

Se frontend em outro domínio:

app.use(cors({ origin: 'https://app.suaempresa.com', credentials: true }));

Sem CORS correto, o browser bloqueia o fetch mesmo com backend funcionando.

Resumo

  • Um endpoint, uma responsabilidade: validar → chamar LLM → responder JSON
  • System prompt fixo no servidor
  • Valide tamanho e tipo da entrada
  • Cookie/session prepara histórico na próxima matéria

Chatbot funcionando ponta a ponta — mas ainda frágil para tráfego real. Na próxima aula colocamos cinto de segurança financeiro: rate limit, cache e métricas.