Como fazer upload de arquivos para o Google Drive em Node.js

Com meu curso de Node.js e MongoDB chegando aos quase 200 inscritos na data em que escrevo este tutorial, muitas ideias tem me sido enviadas com relação a temas para posts e novas aulas no curso. Recentemente, um aluno estava tentando fazer algo que me chamou a atenção: uma API em Node.js que recebesse um arquivo e o enviasse para o Google Drive.

Assim, no artigo de hoje quero mostrar como você pode criar uma integração do Node.js com o Google Drive facilmente. Vamos enviar imagens, mas você pode facilmente adaptar o código para enviar outro tipo de arquivo.

Vamos lá!

Google Drive

Obviamente para que você consiga acompanhar este tutorial você deve ter uma conta no Google Drive. Se você possui um Gmail, automaticamente possui uma pois seus emails são armazenados na mesma que, em seu nível gratuito, fornece 15GB de armazenamento na nuvem. Por cerca de R$70/ano você faz um upgrade para 100GB o que lhe permite salvar muita coisa de maneira rápida e segura.

Caso não possua uma, crie em drive.google.com

Surpreendentemente, existe todo um suporte do Google à tecnologia Node.js, que você confere neste painel do Google Drive APIs. Acesse o painel e clique no botão “Enable Drive API” para que seja possível se integrar com a sua conta do GDrive, conforme a imagem abaixo.

Google Drive APIs
Google Drive APIs

Ao ativar a API, você receberá um Client ID e um Client Secret, que serão necessários para a autenticação na aplicação Node.js.Guarde-os consigo, pois eu não vou fornecer os meus, hehehe. Também lhe é oferecido um arquivo de credenciais (credentials.json), baixe e salve-o na pasta do projeto que vamos criar a seguir.

Criando o projeto em Node

Crie uma pasta na sua máquina chamada gdrive-node e navegue até ela via terminal. Crie um gdrive-auth.js vazio e rode um ‘npm init’ para criar uma aplicação. Depois instale a biblioteca do GDrive usando o comando abaixo:

Lembre-se de ter o seu arquivo credentials.json dentro da pasta do projeto, pois agora vamos carregá-lo na nossa aplicação. Abra o gdrive-auth.js e inclua o seguinte código, adaptado da documentação do Google Drive APIs:

Basicamente o que temos aqui é o código que implementa a autenticação OAuth offline. Ou seja, a sua aplicação vai solicitar um token para a sua conta do Google Drive, que vai ser utilizada pela sua aplicação. Mais tarde usaremos este módulo para chamar a API do GDrive.

Ao executar esse código pela primeira vez no console (node gdrive-auth) você receberá uma URL que deve ser acessada. Copie a URL e ao acessar a página no navegador, você verá a sua conta do Google e ela vai lhe questionar se você quer conceder acesso à essa aplicação Node (Quickstart). Fornecendo a permissão você receberá o token que deve ser informado no terminal novamente, para que seja gravado.

Se tudo ocorrer bem, um teste deve ser realizado automaticamente listando os 10 últimos arquivos adicionados na sua conta do GDrive. Com isso temos um projeto configurado e autenticado para acessar as APIs do Google Drive (essa autenticação é feita apenas uma vez, para obter o arquivo token.json, que fica na raiz da sua aplicação).

Atenção: o escopo da permissão que solicitamos é para gerenciar pastas e arquivos enviados pela própria aplicação. Sendo assim, no primeiro teste não será listado nenhum arquivo pois a aplicação não criou nada ainda. Nos testes subsequentes, no console serão listados os arquivos que já fizemos upload.

Se precisar mudar o escopo de permissão (na variável SCOPES), certifique-se também de excluir o arquivo token.json para que o processo de autenticação reinicie corretamente e ele seja criado com as permissões corretas.

Enviando uma imagem

Agora vamos criar outro módulo no nosso projeto, em um arquivo gdrive.js, com o seguinte conteúdo, adaptado também da documentação oficial:

Neste módulo nós usamos o módulo anterior, de autenticação, e depois enviamos uma imagem passada como argumento para o GDrive criar ela idêntica na nuvem. O argumento fileName é apenas o nome da imagem enquanto que o filePath é o caminho relativo ou literal. Após a criação, será executado um callback com o ID do arquivo no GDrive que acabamos de criar.

Caso queira fazer upload de outros arquivos que não sejam imagens JPG, basta trocar o mime-type.

Agora que temos um módulo de autenticação e um de upload de imagem, vamos criar um index.js para realizar alguns testes:

Aqui estou considerando que você tem um arquivo chamado imagem.jpg dentro da pasta do projeto, no mesmo nível dos módulos. Rode essa aplicação com “node index” e você deve encontrar o novo arquivo na sua conta do GDrive, na raiz do mesmo.

Agora, com as adaptações certas, você pode usar esse módulo em uma web API Express ou mesmo com algum outro framework do Node, para compor uma solução de verdade.

Espero que tenha gostado!

Curtiu o tutorial? Para mais conteúdos bacanas em texto e videoaulas, conheça o meu curso de Node.js e MongoDB clicando no banner abaixo.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

Autorização em Node.js com Passport – Parte 4

Esse tutorial é uma continuação de uma série de artigos sobre segurança em Node.js que iniciei há algum tempo, usando o módulo Passport, o mais popular para autenticação em Node.js. Nas etapas anteriores nós criamos o login, o cadastro, a recuperação de senha e o logout. Hoje vamos criar a camada de autorização, ou seja, uma vez que o usuário esteja autenticado, devemos permitir ou não que ele faça determinadas operações no sistema conforme o seu perfil de acesso.

Atenção: para que possa acompanhar este tutorial é necessário que tenha realizado as etapas anteriores ou ao menos baixado o zip do tutorial anterior, disponível via formulário sempre ao final dos tutoriais. Note que mesmo baixando o zip, você terá de ter o Node.js e o MongoDB instalados na sua máquina (este último rodando) e tem de revisar o arquivo .env para configurar ele com os seus dados (email, string de conexão do banco, etc). Sugiro cadastrar também alguns usuários no seu Mongo para poder usar o sistema corretamente.

Vale salientar que este artigo possui uma versão em videoaula, disponível aos inscritos em meu curso de Node.js e MongoDB.

Vamos lá!

O tripé de segurança

Os elementos mais básicos de segurança em sistemas são o tripé AAA: Autenticação, Autorização e Auditoria. Já falamos extensivamente sobre autenticação nos artigos anteriores e nesse vamos falar de autorização.

A diferença mais básica é que autenticação se resume a dizer quem pode ou não entrar. Já autorização, é o que cada usuário que entrou, pode fazer no sistema.

Existem várias maneiras de implementar isso, das mais elaboradas (usando recursos dedicados de infraestrutura como Active Directory ou LDAP, por exemplo) às mais simples, ter perfis no seu banco de dados e dar match com eles na hora de ver se o usuário pode ou não fazer algo que ele quer. Eu irei neste tutorial pela segunda abordagem.

Espero em artigos futuros falar do terceiro A do tripé: a auditoria, que nada mais é do que o registro das atividades dos usuários no sistema, para consulta futura.

Autorização com ExpressJS e Passport

Como já foi implementado nos tutoriais anteriores e extensivamente explicado, o Passport é um framework genérico para construir de maneira modular a segurança do seu sistema. Nos tutoriais passados usamos ele em conjunto com um conector de MongoDB, pra usar o respectivo banco como sendo a verdade em termos de autenticação do sistema.

Assim, a cada requisição do ExpressJS, usamos o middleware para chamar uma função de verificação e dizer se o usuário pode ou não continuar fazendo aquela requisição ou se deve ser jogado para a tela de login. O que iremos fazer aqui são três coisas:

  • fornecer funções genéricas para que possa ser verificado a autorização do usuário para determinada operação;
  • aplicar tais funções no middleware do Express para, nas chamadas do Passport, avaliar não apenas a autenticação, mas também a autorização;
  • chamar tais funções também na renderização da interface, para garantir que ela atenda às necessidades de segurança conforme usuário autenticado;

Com isso, você não terá um sistema completo, mas um overview de como aplicar tais conceitos e códigos em um sistema real.

Adaptando o cadastro

A primeira coisa que vamos fazer é adaptar a nossa tela de signup para incluir a informação de perfil do usuário. Aqui faremos algo super simples: ao cadastrar um novo usuário, você vai selecionar entre os perfis de administrador e de usuário.

Modifique a view signup.ejs para incluir um select de perfil, como abaixo:

O que vai resultar em uma tela renderizada assim:

Signup com Perfil
Signup com Perfil

O usuário é salvo no banco de dados através de uma função createUser que fica no nosso módulo db.js. Assim, vamo nesse módulo alterar a respectiva função:

Note que você já tem usuários cadastrados no sistema, eles não terão a informação de profile (perfil) salva neles. Aí você tem duas opções: tratar quem não tem perfil como sendo um usuário padrão (que não é administrador), ou fazer um script para alterar todos os usuários já cadastrados no MongoDB para terem a informação de profile também.

Eu vou de primeira opção, vou considerar que por padrão os usuários sem profile NÃO são administradores.

Agora, para fazer o botão Save voltar a funcionar, vamos na rota users.js e vamos editar o código que trata requisições POST no endpoint /signup para que ele espere e salve o dado do perfil também, usando a função que acabamos de alterar, como abaixo:

Com isso, seu cadastro já deve estar funcionando novamente e sugiro que cadastre um novo usuário como sendo administrador, já que os anteriores vão ser todos considerados comuns.

Está curtindo o post? Para uma formação ainda mais completa como programador web recomendo meu livro sobre programação web com Node.js clicando no banner abaixo!

Inserindo autorização por página

Agora que você tem a informação de perfil junto ao usuário cadastrado e cadastrou um usuário administrador, vamos dar alguns privilégios apenas para administradores. Que tal criar uma página que somente eles podem acessar?

Vamos criar uma nova view chamada views/reports.ejs com um conteúdo bem simples, só pra testarmos o conceito:

E vamos criar um novo arquivo de rota em routes/reports.js com apenas o tratamento de um GET, como abaixo:

Note como o global.autheticationMiddeware() é chamado, não apenas nesta, mas em todas rotas do sistema. Isso garante uma centralização das regras de segurança de acesso, que vamos modificar depois.

Registre essa nova rota no app.js como abaixo:

Se você rodar essa aplicação agora e tentar acessar digitando a URL http://localhost:3000/reports no seu navegador, conseguirá acessar normalmente esta página com qualquer usuário devidamente autenticado, mas não conseguirá acessar antes de fazer login (o servidor vai te redirecionar pra página de login se tentar fazer isso).

Mas e se quisermos restringir o acesso a essa página somente a administradores?

Existem várias maneiras de fazer isso. Vou te mostrar uma possibilidade que é centralizando em um módulo a estrutura de decisão para acesso às páginas. Certamente você deve ter várias ideias de como fazer melhor isso, mas essa é a mais simples e didática que consegui imaginar.

Crie na raiz do seu projeto um arquivo permissions.js e vamos criar nele uma lógica que, dada uma requisição, verificamos o usuário autenticado e que página ele está tentando acessar. Batemos essas informações com um switch/case e concedemos ou não a autorização para aquela página.

Note que eu inicio o código criando uma constante e uma função muito simples para dizer se um perfil é ou não é admin. Mas é no module.exports que realmente programo algo de valor pois, dado uma request, eu pego as informações que me interessam e confronto elas com as URLs e suas lógicas de autorização.

Essa é uma abordagem centralizada em um módulo para as regras de autorização. Assim fica bem fácil de manter a aplicação com as regras necessárias. Outra abordagem que já usei no passado era de fazer isso em cada página, ao invés de fazer centralizado e ficou bem ruim de dar manutenção pois era fácil de esquecer de adicionar as regras certas. Aqui, do jeito que está, caso você se esquecer, ao testar já não vai abrir a página e você vai lembrar que deve permitir no arquivo permissions.js.

Mas como fazer esse código de fato passar a valer?

Volte ao app.js e você vai encontrar o código que configura o global.authenticationMiddleware. Você vai modificá-lo para, além de verificar se o usuário está autenticado, verificar se ele tem a permissão necessária para acessar aquela rota, usando o módulo que acabamos de criar, assim:

Agora, toda vez que uma rota for acionada, além de verificar se o usuário está autenticado ou não, vai ser verificado se ele possui permissão para acessar aquela rota. Eu deixei um comentário no código do permissions.js que lhe sugere no futuro verificar o method da request também. Assim, você pode diferenciar tentativas de acesso de tentativas de submissão de formulários, por exemplo.

Esse código, do jeito que está, já funciona e se você acessar sua aplicação e se autenticar, dependendo do usuário que utilizou vai conseguir ou não acessar a página /reports.

Customizando a interface pelo perfil

Para finalizar esse artigo, outra coisa bem comum de ser utilizado em sistemas são as interfaces customizadas conforme a permissão do usuário. Por exemplo, um botão pode ser visto apenas um perfil específico, pois é um botão importante.

Claro, você nunca deve confiar apenas em esconder elementos de interface, mas considerando que já garantimos na seção anterior a segurança por acesso, independente se foi feito digitando a URL ou clicando em links, agora vamos fazer um exemplo bem simples de esconder ou mostrar um elemento de interface baseado no perfil autenticado.

Primeiro, vamos na rota index.js e vamos mudar o código levemente, para informar o perfil do usuário autenticado no model que enviaremos pra view, como abaixo:

Note que a única linha que mexi foi aquela do profile, para passá-lo para a view.

Agora, na view views/index.ejs, vamos editar o código HTML para, baseado no perfil do usuário, exibir ou não um link para a página de relatórios. Basta inserir esse trecho em qualquer parte da página:

Com isso, você pode testar sua aplicação com diferentes usuários e verificar que somente quando se autentica com os administradores é que consegue ver o dito-cujo link que acabou de adicionar.

Link para Admins
Link para Admins

O mais legal é que, independente do usuário ver ou não o link, se ele tentar burlar acessando diretamente a página de reports, a nossa segurança que está no middleware vai barrar ele também.

E por hoje é isso, espero que tenha gostado, deixe suas dúvidas nos comentários. Você pode baixar os fontes usando o formulário que pede seu e-mail e, se quiser participar do meu curso de Node.js e MongoDB, além de fazer parte do meu grupo online de Whatsapp com mais de 100 estudantes de Node, clique no banner abaixo.

Este artigo tem uma versão em videoaula em uma das aulas do meu curso de Nodejs e MongoDB que você confere clicando no banner abaixo.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

O que é um micro servico ou microservice?

Eu já falei sobre esse assunto antes aqui no blog, em outros artigos, sendo que o principal deles e que recomendo que leia na sequência é o Introdução a Arquitetura de Micro Serviços. A ideia do artigo de hoje é explorar mais o que é um microservice e lhe ajudar no entendimento e no desenvolvimento estudando alguns princípios básicos.

O objetivo principal de usar micro serviços (ou microservices) é ajudar o time de engenharia a entregar produtos mais rápido, de maneira mais segura e com qualidade mais alta. Pequenos serviços desacoplados permitem que os times tenham ciclos de desenvolvimento (sprints ou iterações) rápidos e com mínimo impacto ao resto do sistema.

Um micro serviço é uma pequena porção de software que roda de maneira independente, focada em um único e pequeno (daí o nome micro) conjunto de atividades dentro de um conjunto de serviços muito maior, formando uma arquitetura de micro serviços. Resumidamente é uma versão mais enxuta de web services e web APIs que tradicionalmente os desenvolvedores já faziam há anos.

Em uma arquitetura de micro serviços, múltiplos serviços fracamente acoplados (ou seja, com poucas ou nenhuma dependência entre si) trabalham juntos. Cada serviço foca em um único propósito e tem uma alta coesão (congruência, coerência) de comportamentos e dados relacionados. Note que esta última definição inclui três princípios básicos do design de micro serviços, que vamos ver a seguir.

Os três princípios do design de micro serviços

Quando estamos trabalhando em sistemas que usam a arquitetura de micro serviços devemos sempre ter em mente três princípios básicos.

Propósito Único: cada serviço deve focar em um único propósito e fazê-lo bem. Esta é regra é muito semelhante com o Single Responsibility Principle do acrônimo SOLID, muito utilizado em orientação à objetos.

Assim como o SRP do SOLID diz que uma classe Aluno deve se focar apenas em atender às necessidades de um aluno, o princípio do Propósito Único diz que um micro serviço ‘/alunos’ deve se focar apenas em realizar operações sobre alunos. Precisa lidar com questões financeiras do aluno? Crie outro micro serviço para focar nisso. Precisa fazer uma matrícula, onde tem de lidar com o aluno e com questões financeiras? Crie um terceiro micro-serviço que vai chamar os dois anteriores.

Garantir que cada micro serviço tenha um propósito único não é tão fácil e simples, mas é uma das dicas essenciais principalmente quando você está refatorando uma aplicação monolítica em micro serviços.

Baixo Acoplamento:  cada serviço não deve saber nada ou muito pouco dos outros serviços. Eles não devem compartilhar bibliotecas de código, ou apenas o que for genérico e essencial para ter o mínimo de código repetido. Eles devem ter suas próprias validações, seus próprios testes unitários e inclusive não precisam compartilhar sequer a mesma tecnologia ou o mesmo mecanismo de persistência.

Isso tudo para que uma mudança em um serviço não exija mudar outros serviços. Ou que um bug inserido no desenvolvimento de um serviço não atinja o código de outros também. Inclusive a comunicação entre serviços deve ser feita através de interfaces públicas e protocolos leves, funcionando em processos separados.

Este também é um princípio herdado da Orientação à Objetos, onde trabalhamos Loose Coupling ou Low Coupling dentro dos design patterns como sendo um objetivo desejável para garantir boas arquiteturas, muito mencionado no clássico Padrões de Arquitetura de Aplicações Corporativas, do Martin Fowler.

Alta Coesão: cada serviço encapsula todos os comportamentos e dados relacionados juntos. Isso garante que se precisarmos construir uma nova feature sobre um mesmo domínio ou propósito, todas as mudanças devem ser localizadas em um único serviço.

Note que o próprio princípio do Propósito ou Responsabilidade Única dá os subsídios necessários para garantir uma alta coesão, outro tema bem forte nos design patterns da orientação à objetos.

No gráfico abaixo, extraído da Internet, temos a relação entre estes três princípios e como eles se interseccionam para gerar a base desejada para a construção de micro serviços que atendem ao padrão deste tipo de arquitetura.

Princípios de Micro Serviços
Princípios de Micro Serviços

Quando modelamos microservices nós temos que ser disciplinados em seguir ao menos estes três princípios básicos de design. É o único caminho para atingir o potencial pleno de uma arquitetura de microservices. Deixar qualquer um deles de lado fará com que o seu padrão se torne um antipattern ou anti-padrão, algo que vai te levar a resultados ruins, ao invés do que você espera.

Sem um propósito único, por exemplo, cada microservice pode terminar fazendo muitas coisas, crescendo em uma arquitetura de vários serviços monolíticos. Isso fará com que, além de não colher os benefícios completos de uma arquitetura de microservices, você terá um custo operacional alto para manter isso.

Sem um acoplamento baixo, mudanças em um serviço vão afetar outros serviços, lhe impedindo de liberar releases de maneira rápida, frequente e livre de bugs, que é justamente o benefício principal de uma arquitetura dessas. Mais importante ainda, problemas causados por um alto acoplamento podem ser desastrosos como inconsistências de dados ou mesmo perda deles.

E por fim, sem alta coesão, acabamos gerando uma aplicação monolítica distribuída, muito comum no passado e que há décadas sofremos com legados deste tipo onde, apesar de termos muitos serviços, só podemos aplicar novas features em produção se atualizarmos e implantarmos todos eles em produção, muitas vezes em uma ordem específica e ao mesmo tempo. Na minha opinião e de outros engenheiros com ainda mais experiência, isso é pior do que ter uma grande aplicação monolítica porque a complexidade e o custo operacional disso é muito alto.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

O que não é um micro serviço?

Muitas vezes fica mais fácil de entender o que algo é quando definimos o que ele não é, certo?

Um micro serviço não é um serviço que tem um número pequeno de linhas de código ou que faz ‘micro tarefas’. Esta confusão vem do próprio nome, mas não é uma regra e nem é o objetivo desta arquitetura. Os serviços devem possuir a complexidade e quantidade de linhas de código necessárias desde que atendam os três princípios anteriormente descritos.

Um micro serviço não é um serviço construído com a tecnologia mais bacana e hipster que o mercado oferece no momento. Embora a arquitetura de micro serviços te permita sim testar novas tecnologias de maneira muito rápida e fácil, não é um objetivo primário desta arquitetura. Está completamente ok usar qualquer tecnologia que atenda aos princípios anteriores e não faz sentido algum refatorar todos seus micro serviços cada vez que uma nova stack aparece no mercado.

Um micro serviço não tem de ser sempre programado do zero. Quando você está iniciando um processo de refatoração de uma aplicação monolítica, é muito comum que você extraia códigos da aplicação original para os micro serviços e não há problema algum nisso, desde que respeite os três princípios.

Então por hoje é isso, espero que tenha gostado e, caso queira saber mais sobre o assunto, recomendo meu curso de Node.js e MongoDB ou o meu livro sobre micro serviços abaixo.