Como criar um mecanismo de busca com NodeJS + MongoDB

CaptureEste post vem sendo pedido por alguns amigos tem algum tempo já. Isso porque mecanismos de busca tem sido motivo de muito estudo de minha parte desde 2010, quando estava me formando na faculdade e resolvi criar o Busca Acelerada, o primeiro mecanismo de busca que desenvolvi. De lá pra cá tive a oportunidade de desenvolver buscadores de legislação, de fofocas de famosos, de informações da construção civil e muito mais.

A ideia então é eu mostrar, rapidamente, como se cria um site básico de busca que, embora simples, já será muito superior à maioria dos buscadores que os desenvolvedores fazem baseados em SQL. Sim, porque o meu site de busca não usará SQL, mas sim NoSQL, MongoDB para ser mais exato. E para a performance ficar ainda melhor, ele será feito usando a dupla NodeJS + MongoDB que nasceu pra ficarem juntos!

Para conseguir acompanhar este post, você já deve conhecer NodeJS + MongoDB. Caso não conheça, sugiro começar com este tutorial aqui. Também é altamente recomendado que tenha lido esse meu outro post, sobre como criar mecanismos de busca.

Note que não vou ensinar aqui como se criam crawlers ou qualquer outro algoritmo de coleta de informações para popular seu mecanismo, conforme citado no post sobre mecanismo de busca. Considero aqui que você já tem uma massa de dados que deseja oferecer através de um site de busca. Pode ser uma base SQL tradicional, um XML, um Excel, você que sabe.

Com tudo isso em mente, vamos começar!

Veremos nesse artigo:

  1. Configurando o Projeto
  2. Configurando o Banco
  3. Preparando os dados
  4. Configurando o ORM
  5. Criando as views
  6. Programando a busca

Parte 1: Configurando o projeto

Baixe e instale o NodeJS na sua máquina. Crie uma pasta para seus projetos Node e vá até ela via linha de comando, digitando:

Isso irá criar toda a estrutura de pastas do nosso projeto searchengine usando Express (se o comando express não rolar é porque você tem de dar o ‘npm i -g express-generator’).

Agora instale as extensões do mongodb e do mongoose:

Volte ao prompt de comando, navegue até a pasta do projeto searchengine e mande instalar todas as dependências com o comando “npm install”.

Depois, vá na pasta views e dentro do arquivo error.ejs, cole o seguinte código HTML:

Os demais arquivos vamos mexer depois.

Parte 2: Configurando o banco

Aqui você tem duas opções: usar um Mongo em uma plataforma, como a mLab ou a Umbler, e outra é instalar e configurar tudo na unha. Você escolhe.

Opção 1: Solução na nuvem

Crie uma conta na Umbler e você já terá os créditos para gastar sem precisar desembolsar dinheiro neste teste e inclui hospedagem Node.js e Mongo facilmente. A mLab tem uma versão free também que dá conta, mas só nos datacenters americanos e você teria de ter uma conta em outra empresa que possua Node para ter a solução completa.

Na sua conta da Umbler, basta criar um site Node.js e na seção de bancos de dados, criar um banco MongoDB. Anote o host, usuário, senha, etc pra usar depois.

MongoDB
MongoDB

Pronto, você tem o seu banco ok!

Opção 2: Solução local

Baixe e instale o MongoDB (é apenas uma extração de pastas e arquivos, que sugiro que faça em C:/). Agora abra o prompt de comando, navegue até a pasta onde foi instalado o seu Mongo, geralmente em “c:\program files\mongodb\”, acesse a subpasta “server/3.2/bin” e dentro dela digite o comando (certificando-se que exista uma pasta data dentro de C:\mongodb):

Isso irá criar e deixar executando uma instância do MongoDB dentro da pasta data do mongodb. Não feche esse prompt para manter o Mongo rodando local.

Independente da opção escolhida

Agora abra outro prompt de comando (ou use alguma ferramenta de manipulação do MongoDB, como o Studio 3T) e navegue até a pasta bin do MongoDB novamente, digitando o comando “mongo” para iniciar o client do Mongo. Se o seu MongoDB é local, o comando será apenas mongo, no entanto, se for remoto, você vai se conectar da seguinte maneira:

Neste exemplo, meu servidor é tatooine.mongodb.umbler.com, a porta é 36947, meu banco se chama nomeBanco, meu usuário = usuario e minha senha = senha. Altere estas informações conforme a sua configuração!

Depois, chame o comando “use nomeBanco” para se conectar ao banco que usaremos nesse projeto (substituindo nomeBanco pelo nome do seu banco, aqui chamarei de searchengine). Deixe o prompt aberto, usaremos ele em breve para inserir alguns dados de exemplo em nosso banco do buscador.

Parte 3: Preparando os dados

Você pode ter uma base SQL com os dados consolidados do seu negócio e usar o MongoDB apenas como índice e/ou cache de busca. Ou então você pode usar apenas o MongoDB como fonte de dados. Fica à seu critério.

Caso escolha usar SQL e MongoDB, você terá de ter algum mecanismo para mandar os dados que deseja que sejam indexados pelo seu buscador para o Mongo. Este post não cobre migração de dados (mongoimport é o cara aqui), então você deve fazer por sua conta e risco usando os meios que conhecer.

Caso escolha apenas usar o Mongo, você apenas terá de alterar as suas coleções pesquisáveis para incluir um campo com o índice invertido que vamos criar na sequência, com nosso buscador de exemplo.

Em ambos os casos, a sua informação “pesquisável” deve ser armazenada de uma maneira prática de ser pesquisada, o que neste exemplo simples chamaremos de tags. Cada palavra dentro das informações pesquisáveis do seu sistema deve ser transformada em uma tag, que geralmente é um texto todo em maiúsculo (ou minúsculo) e sem acentos ou caracteres especiais.

Por exemplo, se quero tornar pesquisável os nomes dos meus clientes, que no meu SQL estão como “Luiz Júnior”, eu devo normalizá-lo para as tags “LUIZ” e “JUNIOR”, separadas. Assim, quando pesquisarem por luiz, por junior, or luiz junior e por junior luiz, este cliente será encontrado.

Assim, cada registro na sua coleção do MongoDB terá um atributo contendo as suas tags, ou informações pesquisáveis, o que facilmente fazemos com um atributo do tipo array no Mongo. Como abaixo:

Para podermos fazer a busca depois usaremos uma query com um $in ou um $all, que são operadores do Mongo para pesquisar arrays de palavras (seus termos de busca) dentro de arrays de palavras (as tags).

Então, caso esteja migrando dados de um SQL para o Mongo, certifique-se de quebrar e normalizar as informações que deseja pesquisar dentro de um campo tags, como o acima, que será o nosso índice de pesquisa.

Para fins de exemplo, usaremos a massa de dados abaixo (apenas 2 registros) para pré-popular nosso banco com clientes (customers) que já possuem tags normalizadas como mencionado acima. Note que as tags de cada customer são um misto de seus nomes e profissões, o que você pode facilmente fazer com seus dados também.

O comando acima deve ser executado no console cliente do Mongo, logo após o “use searchengine”.

Obviamente existem técnicas de modelagem de banco para mecanismos de busca muito mais elaboradas que essa. Aqui estamos tratando todas as informações textualmente sem classificação do que é cada uma, sem se importar com a ordem ou peso delas, etc. Mas a partir daqui você pode fazer as suas próprias pesquisas para melhorar nosso algoritmo.

Mais pra frente, quando fizermos as nossas pesquisas, vamos fazê-las sempre buscando no campo tags, ao invés de ir nos atributos do documento. Até porque nosso buscador terá apenas um campo de busca, assim como o Google, como veremos adiante.

Mas e a performance disso?

Para resolver este problema vamos criar um índice nesse campo no MongoDB. Mas não é qualquer índice, mas sim um índice multi-valorado pois o campo tags é um array de elementos. O Mongo organiza campos multi-valorados em índices invertidos, que são exatamente um dos melhores tipos de índices básicos que podemos querer em um mecanismo de busca simples como o nosso. Eu já mencionei sobre índices invertidos no post sobre Como criar um mecanismo de busca.

O comando acima deve ser executado no console cliente do Mongo, logo após o “use searchengine”. Todos os customers inseridos a partir de então respeitarão essa regra do índice no campo tags.

Para verificar se funcionou o nosso índice, teste no console cliente do Mongo consultas como essa que traz todos os clientes que possuam a tag LUIZ (isso funciona para lógica OR também, pois recebe um array de possibilidades):

Ou esse que traz todos com as tags LUIZ e JUNIOR (aqui temos lógica AND):

Parte 4: Configurando o ORM

Aqui usaremos o Mongoose, o mais profissional e eficiente ORM para NodeJS + MongoDB na época em que escrevo este post. Fiz testes com um concorrente, o Monk, e a diferença era de 50% a mais de performance, então fique com o Mongoose. Caso não goste de ORMs, neste outro tutorial ensino como usar o MongoDB com o driver nativo.

Conforme definimos na parte 1 deste tutorial, o Mongoose foi instalado como sendo uma das dependências em nosso packages.json, então basta utilizarmos ele em nossos códigos JS.

Crie na raiz do seu projeto um arquivo db.js e dentro coloque a conexão do Mongoose com seu MongoDB, mais o esquema da sua coleção que será consultada (troque a string de conexão apropriadamente).

Note que definimos o schema da nossa coleção customers, incluindo o campo tags como sendo um array de elementos, como já vimos na parte 3 deste tutorial.

Parte 5: Criando as views

Agora vamos criar as views que vamos utilizar: a de pesquisa e a de resultados de pesquisa (chamada de SERP pelos especialistas: Search Engine Results Page). Na verdade vamos fazer as duas em uma, por pura preguiça deste que vos escreve. 😉

Dentro da pasta views do seu projeto, abra index.ejs e cole o seguinte código HTML dentro dele:

Essa é uma página de busca bem simples, com um formulário contendo um campo e um botão de pesquisar. Quando o formulário é submetido, enviamos o usuário para a página de resultados, que vamos criar na sequência.

Salve o arquivo, reinicie seu servidor NodeJS (derrube-o com Ctrl+C se estiver executando e depois inicie-o com npm start a partir da pasta do projeto searchengine).

Capture

O print está meio tosco pois tudo está centralizado. 🙂

Se você pesquisar alguma coisa, como a palavra teste da foto, verá que não vai funcionar, vai dar erro 404, porque ainda não criamos a página de resultado, nem a rota para ela. Na verdade usaremos a mesma página, com algumas modificações.

Parte 6: Programando a busca

Inclua o seguinte código que mistura HTML com JS (característica da view engine EJS que estamos utilizando) logo após seu formulário de busca, na index.ejs:

Esse código vai permitir que, na mesma página de busca, a gente liste os resultados logo abaixo, quando houverem. Quando não existirem resultados, uma mensagem informará adequadamente.

Note que incluímos aí uma série de elementos que vamos ter que programar agora, para que funcionem, entre eles as variáveis results, list e search, que devem ser passadas através da rota que levará o usuário até esta página.

Primeiro, vamos modificar a rota default que leva até a index com a caixa de busca para que ela retorne a variável results como false, indicando que não há resultado a serem exibidos e que a lista de resultados não deve ser exibida pois é um acesso à home.

Substitua o código original da rota default por este. Isso garantirá que a nossa index.ejs continue funcionando mesmo após nossas últimas adições.

Agora, adicione uma nova rota, que atenderá às requisições /search que são feitas quando se clica no botão de busca da index.

Aqui nós pegamos a variável de querystring chamada query e colocamos ela toda em maiúsculas e damos um split pelos espaços, para transformar os termos de busca em um array de termos. O ideal é criar alguma rotina de normalização dos termos de busca, para fazer coisas mais avançadas que essa como remover acentos, etc, mais deixo ao seu critério.

Uma vez com os termos de busca OK, carregamos a variável db com os dados do banco, carregamos o model de customers e damos um find por documentos que possuam as tags que estamos buscando. Nesse caso estou usando um $all, que faz a lógica AND, mas você pode ajustar facilmente por um $in, para fazer a lógica OR ou ainda fazer uma mescla através de análise da pesquisa realizada (tipo o que o Google faz quando digitamos com aspas – AND – ou sem aspas – OR).

O resultado da pesquisa é retornado em um objeto contendo results:true (foi realizada uma pesquisa), search (os termos pesquisados) e list (os documentos retornados). Esse objeto será usado pela lógica anterior que colocamos no EJS que itera sobre os elementos e os renderiza como itens de uma lista não ordenada.

Salve todos os arquivos que não estejam salvos, reinicie o seu servidor Node e mande executar novamente. Você irá entrar na tela index normal, mas quando pesquisar por algum termo que exista no seu banco, verá uma tela de resultados como essa:

Capture

Ou, caso tenha pesquisado por um termo que não existe, verá algo como:

Capture

Claro que você pode fazer muitas modificações nos seus resultados de pesquisa. Você pode querer jogá-los em uma página separada, com um layout mais profissional. Pode querer colocar links neles que levarão o usuário para páginas com detalhes sobre os clientes. Pode querer implementar algum tipo de autocomplete na caixa de busca, usando o Typeahead do Bootstrap. Pode implementar algum mecanismo para sinônimos, plurais, etc para tornar sua busca mais inteligente.

Há milhares de coisas que você pode fazer e eu poderia escrever um ebook sobre isso. Se tivesse tempo no momento. 😀

De qualquer maneira, acho que já consegui dar uma luz à quem nunca criou um buscador antes. Ou quem criou apenas usando LIKE % do SQL tradicional. :/

Caso queira transformar seu algoritmo de busca em uma API, recomendo fortemente dar uma olhada sobre como criar uma API com NodeJS.

Caso vá colocar esse projeto em produção e esteja enfrentando problemas, dê uma olhada nesse post sobre como fazer deploy em servidores Windows.

Depois que colocar ele em produção, visando obter muitas visitas vindas do Google, sugiro ler esse meu artigo aqui sobre SEO para mecanismos de busca.

Eu ministrei recentemente uma palestra sobre este assunto, cujos slides estão abaixo (o código está ligeiramente diferente):

Um abraço, e até a próxima!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!

O que achou desse artigo?
[Total: 6 Média: 3.2]