Skip to content

Instantly share code, notes, and snippets.

@lucis
Created October 23, 2025 15:10
Show Gist options
  • Save lucis/612758c3f9ed0389b5b40d042c438c87 to your computer and use it in GitHub Desktop.
Save lucis/612758c3f9ed0389b5b40d042c438c87 to your computer and use it in GitHub Desktop.

Lições de API Design: Drizzle ORM v2

Exploração gerada no Claude a partir da proposta da Relational API v2


O Problema que Vale a Pena Resolver

ORMs sempre viveram num limbo desconfortável: ou você escreve SQL puro e perde type-safety, ou usa um ORM que te trava quando precisa de algo complexo. Drizzle v2 ataca esse problema de frente.

A mudança parece pequena à primeira vista:

// v1
where: (pets, { eq }) => eq(pets.ownerId, 1)

// v2
where: { ownerId: 1 }

Mas essa simplicidade superficial esconde algo mais profundo.


Progressive Disclosure of Complexity

A API funciona em camadas. Você começa simples e descobre poder conforme precisa:

// Camada 1: O óbvio
where: { ownerId: 1 }

// Camada 2: Combinações (AND implícito)
where: { ownerId: 1, name: "pikachu" }

// Camada 3: Operadores explícitos
where: { OR: [{ ownerId: 1 }, { name: "pikachu" }] }

// Camada 4: Comparações complexas
where: { ownerId: { gt: 1, lte: 10 } }

// Camada 5: Escape hatch
where: { RAW: (pets, { sql }) => sql`lower(${pets.name}) = 'pikachu'` }

Cada camada te dá mais controle, mas você nunca é forçado a conhecer todas de cara. Aprende conforme cresce.


A Lei do Escape Hatch

Toda abstração precisa de uma saída de emergência. O momento em que você pensa "eu só preciso fazer X mas a API não deixa" é quando você abandona a ferramenta.

Drizzle entendeu isso. Sempre tem um RAW esperando quando você precisar. A API não tenta ser mais inteligente que você.

Isso aparece em todo canto:

  • CSS Modules → style={{ ... }}
  • React Hooks → useEffect
  • Git → --force

As melhores ferramentas sabem quando sair do caminho.


Context Over Imports

Compare as duas versões de declarar relações:

// v1: imports intermináveis
import { relations } from 'drizzle-orm';
import { users } from './users';
import { posts } from './posts';

// v2: contexto rico
relations(schema, (r) => ({
  posts: {
    author: r.one({
      from: r.users.id,
      to: r.posts.authorId,
    })
  }
}))

O r carrega todo o conhecimento necessário. Você não perde tempo gerenciando imports - pensa direto no relacionamento.

Reduzir imports não é só conveniência. É reduzir carga cognitiva. Cada import é um contexto que você precisa carregar na cabeça.


Semântica Sobre Precisão Técnica

Eles trocaram fields e references por from, to, one, many, through.

Tecnicamente? Talvez menos preciso.
Na prática? Infinitamente mais claro.

A API fala a língua do usuário, não da implementação interna. Quando você escreve r.one({ from: ..., to: ... }), você está pensando na direção do relacionamento, não em foreign keys.

Bom naming é sobre intenção, não exatidão.


Convenção vs Configuração (Feito Direito)

Múltiplos filtros viram AND automaticamente:

where: { ownerId: 1, name: "pikachu" }

Mas quando você quer OR, precisa ser explícito:

where: { OR: [{ ownerId: 1 }, { name: "pikachu" }] }

A convenção cobre 80% dos casos. Os outros 20% pedem clareza, não magia.

Configuração demais vira burocracia. Convenção demais vira surpresa. O truque é saber onde colocar cada uma.


A Regra 80/20 Invertida

A maioria das APIs falha porque otimiza 100% para um lado:

  • Simples demais → trava em casos complexos (Prisma antigo)
  • Complexo demais → curva de aprendizado brutal (Hibernate)

Drizzle inverte: design para 80% de casos simples, mas obsessivamente suporte os 20% complexos.

Você consegue fazer where: { id: 1 } (trivial) e where: { RAW: sql... } (total liberdade) na mesma API. Sem conflito.


Type Safety com Porta de Saída

A API é totalmente tipada. TypeScript conhece cada campo, cada relação, cada operador possível.

Mas quando o sistema de tipos te trava? RAW está lá. Um any controlado, consciente, documentado.

Type safety vale muito. Mas tiranizar o usuário com tipos vale menos que zero.


O Valor de Admitir Erro

Drizzle v1 tinha problemas. Naming confuso, imports demais, many-to-many complicado.

Ao invés de defender ("foi uma escolha consciente"), eles ouviram feedback por um ano e refizeram do zero.

v2 contradiz v1 em vários lugares. E está tudo bem.

Essa humildade é rara. A maioria das empresas defende decisões ruins por orgulho. Drizzle admitiu "erramos" e consertou.

Produtos mediocres mantêm consistência a todo custo. Produtos extraordinários mantêm qualidade a todo custo.


Aplicações Universais

Esses princípios aparecem em todo canto:

CLI Design:

git commit -m "fix"              # simples
git commit --amend --no-edit     # avançado  
git commit --allow-empty         # escape hatch

UI/UX:

  • Figma: retângulos simples → Auto Layout → plugins
  • Linear: issues básicas → custom views → API

Produtos:

  • Features padrão → customização → white-label
  • Self-service → customer success → enterprise

Código:

  • Funções → classes → metaprogramação
  • Components → hooks → refs

Sempre três camadas: simples por padrão, poder quando necessário, liberdade como último recurso.


A Lição Final

Bom design de API tem menos a ver com sintaxe e mais com filosofia:

  1. Deixe usuários começar sem pensar
  2. Deixe usuários crescer sem refazer
  3. Deixe usuários escapar sem travar

Drizzle v2 acerta os três.

E talvez o mais importante: mostra que vale a pena ouvir quem usa seu produto. Um ano de feedback resultou numa API radicalmente melhor.

Isso importa mais que qualquer decisão técnica individual.


Referências

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment