Primeiros passos — infraestrutura e as primeiras linhas de código
Dogwalk·

Primeiros passos — infraestrutura e as primeiras linhas de código

9 min de leitura← Voltar para timeline

Ter a ideia foi fácil. Colocar ela de pé foi outra história.

Quatro dias depois do estalo inicial, eu estava pronto para começar a codar de verdade. Mas antes precisava responder uma pergunta crucial: qual stack usar?

A escolha da stack

React 19 tinha acabado de sair, Vite 8 prometia build imediato, e FastAPI era a escolha óbvia para o backend — performance boa, tipagem nativa com Pydantic, e documentação automática.

# O comando que deu início a tudo
npm create vite@latest dogwalk -- --template react-ts
cd dogwalk
npm install

Parece simples agora, mas na época enfrentei uns bons perrengues. Configuração de ESLint com React 19 deu briga, o Vite 8 tinha acabado de sair com algumas breaking changes, e o Stripe me fez perder uma tarde inteira porque esqueci de configurar o webhook no ambiente de desenvolvimento.

Comparação de tecnologias

Antes de bater o martelo, fiz uma planilha comparativa:

Critério React 19 Vue 3.5 Next.js Svelte
Build tool Vite 8 Vite 8 Webpack Vite 8
SSR/SSG SPA puro SPA puro SSR nativo SPA puro
TypeScript Nativo Nativo Nativo Nativo
Curva aprendizado Conheço bem Razoável Conheço bem Aprenderia
Ecossistema maturidade Gigante Grande Grande Crescendo
Bundle size (base) ~40 KB ~33 KB ~70 KB ~5 KB
Veredito ** Escolhido** 2ª opção Overkill p/ SPA Risco

Backend:

Critério FastAPI Flask Express Django
Performance Assíncrono Síncrono Assíncrono Síncrono
Tipagem Pydantic (JS) (médio)
Docs automáticas Swagger
Async DB asyncpg gevent Prisma
ORM SQLAlchemy SQLAlchemy Prisma ORM próprio
Peso Leve Leve Leve Pesado
Veredito ** Escolhido** Sem async Sem tipagem Pesado demais

A primeira rota: cadastro de usuário

Com a stack definida, escrevi as primeiras rotas de verdade. O cadastro de usuário foi o primeiro endpoint que conectou frontend e backend:

# backend/app/routers/auth.py — primeira rota funcional
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel, EmailStr
import bcrypt
from app.database import get_db

router = APIRouter(prefix="/auth", tags=["auth"])

class RegisterRequest(BaseModel):
  name: str
  email: EmailStr
  password: str
  role: str  # 'tutor' | 'passeador'

class UserResponse(BaseModel):
  id: str
  name: str
  email: str
  role: str
  created_at: str

@router.post("/register", response_model=UserResponse)
async def register(req: RegisterRequest):
  if req.role not in ("tutor", "passeador"):
  raise HTTPException(400, "Role inválida")

  hashed = bcrypt.hashpw(
  req.password.encode(), bcrypt.gensalt()
  ).decode()

  async with get_db() as db:
  # Verifica se email já existe
  existing = await db.fetchrow(
  "SELECT id FROM users WHERE email = $1", req.email
  )
  if existing:
  raise HTTPException(409, "Email já cadastrado")

  # Cria usuário + profile em transação
  user = await db.fetchrow(
  """INSERT INTO users (name, email, password_hash)
  VALUES ($1, $2, $3)
  RETURNING id, name, email, created_at""",
  req.name, req.email, hashed
  )

  await db.execute(
  """INSERT INTO profiles (user_id, name, role)
  VALUES ($1, $2, $3)""",
  user["id"], req.name, req.role
  )

  return {
  "id": str(user["id"]),
  "name": user["name"],
  "email": user["email"],
  "role": req.role,
  "created_at": user["created_at"].isoformat(),
  }

O uso de asyncpg diretamente (sem ORM) foi intencional — eu queria sentir o banco antes de abstrair. Só depois adicionei SQLAlchemy para consultas mais complexas.

Docker e PostgreSQL

O banco rodava em Docker desde o início. A primeira docker-compose.yml era simples:

# docker-compose.yml — versão 0.1
version: "3.9"

services:
  db:
  image: postgres:18-alpine
  environment:
  POSTGRES_DB: dogwalk
  POSTGRES_USER: dogwalk
  # opcional: acesso configurado via env externo
  POSTGRES_ACCESS: local_dev
  ports:
  - "5432:5432"
  volumes:
  - pgdata:/var/lib/postgresql/data
  - ./backend/migrations:/docker-entrypoint-initdb.d

volumes:
  pgdata:

O volume de migrations montado em docker-entrypoint-initdb.d fazia o PostgreSQL executar os SQLs de schema na primeira inicialização. Era tosco, mas funcionava.

A primeira migration (que sobreviveu)

-- backend/migrations/001_users_profiles.sql
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL,
  password_hash TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE profiles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  role TEXT NOT NULL CHECK (role IN ('tutor', 'passeador')),
  phone TEXT,
  avatar_url TEXT,
  city TEXT,
  rating DECIMAL(2,1) DEFAULT 0.0,
  is_verified BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_profiles_user_id ON profiles(user_id);
CREATE INDEX idx_profiles_city ON profiles(city);
CREATE INDEX idx_profiles_role ON profiles(role);

Essa migration é a base de tudo até hoje. Claro, depois vieram mais 15 tabelas (pets, bookings, walks, gps_points, reviews, chat_messages, notifications, financial_transactions, etc.), mas a estrutura de users + profiles se manteve.

As primeiras lutas

Olhando hoje o deploy-workflow.sh, parece um script de boa. Mas ele nasceu de um sábado inteiro tentando entender por que o build quebrava em produção mas não em dev.

#!/bin/bash
# deploy-workflow.sh — versão 0.1, 28/05/2026
echo " Buildando Dogwalk..."
npm run build && echo " Build OK" || echo " Build falhou"

Sim, era literalmente isso no começo. Cresceu com condicionais, health checks, logs, e hoje tem umas 80 linhas. Mas todo script grande começa pequeno.

Configuração de ambiente

O gerenciamento de variáveis de ambiente foi outro aprendizado. O primeiro .env era mínimo:

# .env — primeira versão
DOGWALK_JWT_CONFIG=dev-mode
DOGWALK_DB_URL=postgresql+asyncpg://dogwalk@127.0.0.1:5432/dogwalk
VITE_API_URL=http://localhost:8080
# chaves de pagamento configuradas via env
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_exemplo

Depois veio o EnvironmentFile= no systemd, a separação .env.development / .env.production, e o openssl rand -hex 32 para gerar secrets de verdade. Mas no começo, era só isso.

PostgreSQL e a primeira migration

A escolha do banco foi PostgreSQL rodando em Docker. Nada de ORM complexo no começo — SQL puro mesmo, com asyncpg no FastAPI.

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL,
  role TEXT CHECK (role IN ('tutor', 'passeador')) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

Essa foi literalmente a primeira tabela. Ela existe até hoje, com algumas colunas a mais e um punhado de índices.

O que aprendi

Que setup de projeto é traiçoeiro — parece que você tá progredindo quando na verdade só tá configurando ferramentas. Mas sem essa base, o resto desmorona. Cada hora gasta na infra no começo economizou dias de dor de cabeça depois.

Pitfalls que marcaram

  1. bind 0.0.0.0 vs 127.0.0.1 — No começo o uvicorn ouvia em 0.0.0.0, expondo o backend na rede local. Corrigi para 127.0.0.1 e adicionei --no-server-header para não vazar versão.

  2. Timezone naive vs aware — PostgreSQL rejeita comparar timestamp without time zone com datetime.now(timezone.utc). Solução: datetime.now(timezone.utc).replace(tzinfo=None).

  3. MemoryMax no WSL — Coloquei MemoryMax=2G no systemd e o backend reiniciava a cada 3 minutos. WSL2 não suporta cgroup v2 direito. Removi a diretiva e tudo ficou estável.

  4. Rollup/Rolldown — Vite 8 usa Rolldown (Rust). Ele não aceita imports no final do arquivo, e closures inline com block body em JSX quebram. Precisei extrair subcomponentes em vários lugares.


Comandos úteis

~/lifelog — bash
$cat about.txt
╔══════════════════════════════════════╗
║  Samuel Medeiros                    ║
║  Senior Software Engineer           ║
║  Stack: Python · TypeScript · Rust  ║
║  Projetos: Arachne, Dogwalk,        ║
║            Capivara, TatuEngine      ║
╚══════════════════════════════════════╝
      
$