Ao desenvolver o Photofy como um projeto pessoal aqui em Paulista, percebi que a busca tradicional por "cidade" era muito frustrante. Um fotógrafo que mora no Janga, em Paulista, está colado ao bairro de Rio Doce, em Olinda, mas se o cliente buscasse apenas por profissionais em "Recife", o sistema simplesmente ignorava quem estava a poucos quarteirões de distância por causa de um limite invisível no banco de dados. Percebi que a geolocalização não é apenas um recurso estético; é o que faz o contrato ser viável ou não, reduzindo o custo de deslocamento para o fotógrafo e trazendo resultados reais para o cliente.
O Problema da Fronteira Invisível
O erro comum é acreditar que a geografia respeita o texto. Buscar apenas por city: 'Recife' é ineficiente porque o banco de dados realiza uma comparação exata de caracteres, ignorando que Olinda é vizinha de Recife. Dessa forma, o sistema desconsidera a proximidade física entre quem busca e os fotógrafos da região, limitando os resultados a nomes em uma tabela em vez da distância real entre as pessoas.

Figura 1: A continuidade urbana na Região Metropolitana do Recife. Note como as fronteiras municipais são ignoradas pela proximidade real entre o Janga (Paulista) e Rio Doce (Olinda). Fonte: © OpenStreetMap contributors.
Minha primeira ideia foi a mais óbvia (o famoso Naive Approach): puxar todos os usuários do banco para o Node.js e rodar a fórmula de Haversine na mão. Mas logo vi o problema: quando o Photofy crescer para 10 ou 20 mil fotógrafos, eu ia sobrecarregar a memória do servidor processando um array gigante a cada busca. A saída mais inteligente que encontrei foi usar o PostGIS, deixando essa matemática pesada para a engine do PostgreSQL, que já é otimizada para isso.
Por que PostGIS? (Teoria Rápida)
Para o projeto funcionar, precisei decidir entre dois tipos de dados espaciais:
- Geometry: Trata a Terra como um plano cartesiano.
- Geography: Trata a Terra como um esferoide (curvo).
Escolhi o tipo Geography com o padrão SRID 4326 (WGS 84) — o mesmo usado pelo GPS dos smartphones. Isso me permitiu trabalhar com distâncias em metros de forma nativa.
Para a performance, implementei o índice GiST (Generalized Search Tree). Diferente do B-Tree comum, o GiST trabalha com "Bounding Boxes" (caixas delimitadoras). Na prática, o banco descarta instantaneamente áreas enormes (como ignorar um estado inteiro) antes mesmo de começar a filtrar os fotógrafos disponíveis em Pernambuco.
Mão na Massa: Configurando o Banco
Na hora de codar com NestJS e Prisma, a realidade foi um pouco mais desafiadora. Como o Prisma ainda é limitado com tipos espaciais nativos, o "pulo do gato" foi usar o tipo Unsupported no schema e editar a migration manualmente.
1. O Schema (prisma.schema)
model User {
// ... outros campos
// O tipo Unsupported força o Prisma a ignorar a validação JS
// mas cria a coluna correta no banco
location Unsupported("geography(Point, 4326)")?
// Índice GiST é OBRIGATÓRIO para performance
@@index([location], name: "location_idx", type: Gist)
}
2. A Migration SQL
O Prisma gera o arquivo SQL, mas precisei adicionar manualmente o comando para habilitar a extensão no topo:
-- Habilitar a extensão ANTES de criar a tabela
CREATE EXTENSION IF NOT EXISTS postgis;
-- Alterar a tabela (gerado pelo Prisma)
ALTER TABLE "users" ADD COLUMN "location" geography(Point, 4326);
Integrando ao Backend (NestJS)
Com o banco pronto, conectei o backend via Raw Queries ($queryRaw) para garantir que o índice GiST fosse realmente utilizado. Para converter endereço em coordenada (Geocoding), usei a API do Nominatim.
1. O Serviço de Geocoding (Simplificado)
async getCoordinates(address: string) {
const params = new URLSearchParams({ q: address, format: 'json', limit: '1' });
// O header User-Agent é obrigatório para a API do Nominatim
const response = await fetch(`https://nominatim.openstreetmap.org/search?${params}`, {
headers: { 'User-Agent': 'Photofy-Project' },
});
const data = await response.json();
return data[0] ? { lat: parseFloat(data[0].lat), lng: parseFloat(data[0].lon) } : null;
}
2. A Busca Espacial
Aqui usamos o Raw Query para acessar as funções ST_DWithin (filtro de raio usando índice) e ST_Distance (calculo exato de metros).
// Interface essencial para o TypeScript entender o retorno do banco
interface UserWithDistance {
id: string;
distance: number;
}
async findNearbyPhotographers(lat: number, lng: number, radiusKm: number) {
// ST_SetSRID cria um ponto GPS válido (WGS 84)
const result = await this.prisma.$queryRaw<UserWithDistance[]>`
SELECT
id, name,
ST_Distance(
location,
ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326)::geography
) as distance
FROM users
WHERE
ST_DWithin(
location,
ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326)::geography,
${radiusKm * 1000}
)
ORDER BY distance ASC
`;
return result;
}
Conclusão: O Banco não é apenas um Depósito de Dados
Essa jornada no Photofy me tirou da zona de conforto do "Code First" e me fez entender que o banco de dados não é apenas um depósito de dados, mas uma engine de computação poderosa.
Delegar a lógica espacial para o PostGIS limpou meu código e protegeu o Event Loop do Node.js. Hoje, o sistema está pronto para escalar para milhares de usuários com custo computacional $O(\log N)$, conectando pessoas em Paulista, Olinda e Recife com base na distância real e não em simples comparações de texto.
Nota sobre o Código
Este artigo é um recorte técnico de um estudo de caso em um projeto pessoal. Por razões de segurança e estágio atual de desenvolvimento, o repositório completo permanece privado. No entanto, os trechos de código compartilhados acima são representações fiéis da lógica utilizada no sistema.
Referências
NESTJS. Documentation: A progressive Node.js framework. 2024. Disponível em: https://docs.nestjs.com/.
OPENSTREETMAP FOUNDATION. Nominatim API v4.3.2 Documentation. 2023. Disponível em: https://nominatim.org/release-docs/latest/api/Overview/.
POSTGIS PROJECT. PostGIS 3.4.0 Manual. 2023. Disponível em: https://postgis.net/docs/manual-3.4/.
POSTGRESQL GLOBAL DEVELOPMENT GROUP. PostgreSQL 16.0 Documentation. 2023. Disponível em: https://www.postgresql.org/docs/16/index.html.
PRISMA DATA, INC. Prisma Documentation: Working with MongoDB, PostgreSQL, and more. 2024. Disponível em: https://www.prisma.io/docs.
Top comments (0)