Deixe o LoopBack fazer isso: um passo a passo do Node API Framework com o qual você sonhou

Publicados: 2022-03-11

É desnecessário mencionar a crescente popularidade do Node.js para desenvolvimento de aplicativos. O eBay executa um serviço de API Node de produção desde 2011. O PayPal está reconstruindo ativamente seu front-end no Node. O site móvel do Walmart se tornou o maior aplicativo Node em termos de tráfego. No fim de semana de Ação de Graças em 2014, os servidores do Walmart processaram 1,5 bilhão de solicitações, 70% das quais foram entregues por meio de dispositivos móveis e alimentadas por Node.js. No lado do desenvolvimento, o gerenciador de pacotes Node (npm) continua crescendo rapidamente, ultrapassando recentemente 150.000 módulos hospedados.

Enquanto Ruby tem Rails e Python tem Django, a estrutura dominante de desenvolvimento de aplicativos para Node ainda não foi estabelecida. Mas há um poderoso concorrente ganhando força: LoopBack, uma estrutura de API de código aberto criada pela empresa StrongLoop de San Mateo, Califórnia. O StrongLoop é um importante contribuinte para a versão mais recente do Node, sem mencionar os mantenedores de longa data do Express, um dos frameworks Node mais populares existentes.

Vamos dar uma olhada mais de perto no LoopBack e seus recursos, transformando tudo em prática e criando um aplicativo de exemplo.

O que é LoopBack e como funciona com o Node?

LoopBack é uma estrutura para criar APIs e conectá-las a fontes de dados de back-end. Construído sobre o Express, ele pode receber uma definição de modelo de dados e gerar facilmente uma API REST de ponta a ponta totalmente funcional que pode ser chamada por qualquer cliente.

O LoopBack vem com um cliente integrado, API Explorer . Usaremos isso para facilitar a visualização dos resultados do nosso trabalho e para que nosso exemplo possa se concentrar na construção da própria API.

É claro que você precisará do Node instalado em sua máquina para acompanhar. Venha aqui. npm vem com ele, então você pode instalar os pacotes necessários facilmente. Vamos começar.

Crie um esqueleto

Nosso aplicativo gerenciará pessoas que gostariam de doar presentes, ou coisas que simplesmente não precisam mais, para alguém que possa precisar deles. Assim, os usuários serão Doadores e Receptores. Um Doador pode criar um novo presente e ver a lista de presentes. Um Destinatário pode ver a lista de presentes de todos os usuários e pode reivindicar qualquer um que não tenha sido reivindicado. Claro, poderíamos construir Doadores e Receptores como papéis separados na mesma entidade (Usuário), mas vamos tentar separá-los para ver como construir relações no LoopBack. O nome deste aplicativo inovador será Givesomebody .

Instale as ferramentas de linha de comando StrongLoop por meio do npm:

 $ npm install -g strongloop

Em seguida, execute o gerador de aplicativos do LoopBack:

 $ slc loopback _-----_ | | .--------------------------. |--(o)--| | Let's create a LoopBack | `--------- | application! | ( _U`_ ) '--------------------------' /___A___\ | ~ | __'.___.'__ ` |° Y ` ? What's the name of your application? Givesomebody

Vamos adicionar um modelo. Nosso primeiro modelo se chamará Gift. O LoopBack solicitará a fonte de dados e a classe base. Como ainda não configuramos a fonte de dados, podemos colocar db (memory) . A classe base é uma classe de modelo gerada automaticamente, e queremos usar PersistedModel neste caso, pois ela já contém todos os métodos CRUD usuais para nós. Em seguida, LoopBack pergunta se deve expor o modelo por meio de REST (sim), e o nome do serviço REST. Pressione enter aqui para usar o padrão, que é simplesmente o plural do nome do modelo (no nosso caso, gifts ).

 $ slc loopback:model ? Enter the model name: Gift ? Select the data-source to attach Gift to: (Use arrow keys) ❯ db (memory) ? Select model's base class: (Use arrow keys) Model ❯ PersistedModel ? Expose Gift via the REST API? (Y/n) Yes ? Custom plural form (used to build REST URL):

Por fim, damos os nomes das propriedades, seus tipos de dados e sinalizadores obrigatórios/não obrigatórios. O presente terá propriedades de name e description :

 Let's add some Gift properties now. Enter an empty property name when done. ? Property name: name invoke loopback:property ? Property type: (Use arrow keys) ❯ string ? Required? (y/N)Yes

Insira um nome de propriedade vazio para indicar que você terminou de definir as propriedades.

O gerador de modelo criará dois arquivos que definem o modelo no common/models do aplicativo: gift.json e gift.js . O arquivo JSON especifica todos os metadados sobre a entidade: propriedades, relações, validações, funções e nomes de métodos. O arquivo JavaScript é usado para definir comportamento adicional e especificar ganchos remotos a serem chamados antes ou depois de certas operações (por exemplo, criar, atualizar ou excluir).

As outras duas entidades modelo serão nossos modelos Doador e Receptor. Podemos criá-los usando o mesmo processo, exceto que desta vez vamos colocar User como a classe base. Ele nos dará algumas propriedades como nome de username , password , e- email para uso. Podemos adicionar apenas nome e país, por exemplo, para ter uma entidade completa. Para o Receptor, queremos adicionar o endereço de entrega também.

Estrutura do projeto

Vamos dar uma olhada na estrutura do projeto gerado:

Estrutura do projeto

Os três diretórios principais são: - /server – Contém scripts de aplicativos de nós e arquivos de configuração. - /client – ​​Contém .js, .html, .css e todos os outros arquivos estáticos. - /common – Esta pasta é comum ao servidor e ao cliente. Os arquivos de modelo vão aqui.

Aqui está um detalhamento do conteúdo de cada diretório, retirado da documentação do LoopBack:

Arquivo ou diretório Descrição Como acessar em código
Diretório de aplicativos de nível superior
package.json Especificação padrão do pacote npm. Veja pacote.json N / D
/diretório do servidor - arquivos de aplicativos do nó
server.js Arquivo principal do programa aplicativo. N / D
config.json Configurações do aplicativo. Consulte config.json. app.get('setting-name')
datasources.json Arquivo de configuração da fonte de dados. Consulte datasources.json. Para obter um exemplo, consulte Criar nova fonte de dados . app.datasources['datasource-name']
model-config.json Arquivo de configuração do modelo. Consulte model-config.json. Para mais informações, veja Conectando modelos a fontes de dados . N / D
middleware.json Arquivo de definição de middleware. Para obter mais informações, consulte Definindo middleware. N / D
/boot Adicione scripts para executar a inicialização e configuração. Consulte scripts de inicialização. Os scripts são executados automaticamente em ordem alfabética.
/diretório do cliente - arquivos do aplicativo cliente
README.md Os geradores de LoopBack criam um arquivo README vazio no formato markdown. N / D
De outros Adicione seus arquivos HTML, CSS e JavaScript do cliente.
/diretório comum - arquivos de aplicativos compartilhados
diretório /models Arquivos de modelo personalizado:
  • Arquivos JSON de definição de modelo, por convenção denominados model-name .json ; por exemplo customer.json .
  • Scripts de modelo personalizados por convenção denominados model-name .js ; por exemplo, customer.js .
Para obter mais informações, consulte Arquivo JSON de definição de modelo e Personalizando modelos.
Nó:
myModel = app.models.myModelName

Construir relacionamentos

Em nosso exemplo, temos alguns relacionamentos importantes para modelar. Um Doador pode doar muitos Presentes, o que dá a relação Doador tem muitos Presentes . Um Receptor também pode receber muitos Presentes, então também temos a relação Receptor tem muitos Presentes . Por outro lado, o Presente pertence ao Doador e também pode pertencer ao Destinatário se o Destinatário optar por aceitá-lo. Vamos colocar isso na linguagem do LoopBack.

 $ slc loopback:relation ? Select the model to create the relationship from: Donor ? Relation type: has many ? Choose a model to create a relationship with: Gift ? Enter the property name for the relation: gifts ? Optionally enter a custom foreign key: ? Require a through model? No

Observe que não há modelo de passagem; estamos apenas mantendo a referência ao Dom.

Se repetirmos o procedimento acima para o Receptor e adicionarmos dois pertences às relações para Presente, realizaremos nosso projeto de modelo em um lado de trás. O LoopBack atualiza automaticamente os arquivos JSON dos modelos para expressar exatamente o que acabamos de fazer através dessas caixas de diálogo simples:

 // common/models/donor.json ... "relations": { "gifts": { "type": "hasMany", "model": "Gift", "foreignKey": "" } }, ...

Adicionar uma fonte de dados

Agora vamos ver como anexar uma fonte de dados real para armazenar todos os dados do nosso aplicativo. Para os propósitos deste exemplo, usaremos o MongoDB, mas o LoopBack possui módulos para conexão com Oracle, MySQL, PostgreSQL, Redis e SQL Server.

Primeiro, instale o conector:

 $ npm install --save loopback-connector-mongodb

Em seguida, adicione uma fonte de dados ao seu projeto:

 $ slc loopback:datasource ? Enter the data-source name: givesomebody ? Select the connector for givesomebody: MongoDB (supported by StrongLoop)

A próxima etapa é configurar sua fonte de dados em server/datasources.json . Use esta configuração para um servidor MongoDB local:

 ... "givesomebody": { "name": "givesomebody", "connector": "mongodb", "host": "localhost", "port": 27017, "database": "givesomebody", "username": "", "password": "" } ...

Por fim, abra server/model-config.json e altere a datasource de dados de todas as entidades que queremos que persistam no banco de dados para "givesomebody" .

 { ... "User": { "dataSource": "givesomebody" }, "AccessToken": { "dataSource": "givesomebody", "public": false }, "ACL": { "dataSource": "givesomebody", "public": false }, "RoleMapping": { "dataSource": "givesomebody", "public": false }, "Role": { "dataSource": "givesomebody", "public": false }, "Gift": { "dataSource": "givesomebody", "public": true }, "Donor": { "dataSource": "givesomebody", "public": true }, "Receiver": { "dataSource": "givesomebody", "public": true } }

Testando sua API REST

É hora de ver o que construímos até agora! Usaremos a incrível ferramenta integrada, API Explorer , que pode ser usada como cliente para o serviço que acabamos de criar. Vamos tentar testar as chamadas da API REST.

Em uma janela separada, inicie o MongoDB com:

 $ mongod

Execute o aplicativo com:

 $ node .

No seu navegador, acesse http://localhost:3000/explorer/ . Você pode ver suas entidades com a lista de operações disponíveis. Tente adicionar um Donor com uma chamada POST /Donors .

Testando sua API 2

Testando sua API 3

O API Explorer é muito intuitivo; selecione qualquer um dos métodos expostos e o esquema de modelo correspondente será exibido no canto inferior direito. Na área de texto de data , é possível escrever uma solicitação HTTP personalizada. Após o preenchimento da solicitação, clique no botão “Experimentar” e a resposta do servidor será exibida abaixo.

Testando sua API 1

Autenticação de usuário

Como mencionado acima, uma das entidades que vem pré-construídas com o LoopBack é a classe User. O usuário possui métodos de login e logout e pode ser vinculado a uma entidade AccessToken que mantém o token do usuário específico. Na verdade, um sistema completo de autenticação de usuário está pronto para sair da caixa. Se tentarmos chamar /Donors/login por meio do API Explorer , aqui está a resposta que obtemos:

 { "id": "9Kvp4zc0rTrH7IMMeRGwTNc6IqNxpVfv7D17DEcHHsgcAf9Z36A3CnPpZJ1iGrMS", "ttl": 1209600, "created": "2015-05-26T01:24:41.561Z", "userId": "" }

O id é na verdade o valor do AccessToken, gerado e persistido no banco de dados automaticamente. Como você vê aqui, é possível definir um token de acesso e usá-lo para cada solicitação subsequente.

Autenticação de usuário

Métodos Remotos

Um método remoto é um método estático de um modelo, exposto em um ponto de extremidade REST personalizado. Métodos remotos podem ser usados ​​para executar operações não fornecidas pela API REST do modelo padrão do LoopBack.

Além dos métodos CRUD que tiramos da caixa, podemos adicionar quantos métodos personalizados quisermos. Todos eles devem ir para o arquivo [model].js . No nosso caso, vamos adicionar um método remoto ao modelo Gift para verificar se o presente já está reservado e outro para listar todos os presentes que não estão reservados.

Primeiro, vamos adicionar uma propriedade adicional ao modelo chamada reserved . Basta adicionar isso às propriedades em gift.json :

 ... "reserved": { "type": "boolean" } ...

O método remoto em gift.js deve ser algo assim:

 module.exports = function(Gift) { // method which lists all free gifts Gift.listFree = function(cb) { Gift.find({ fields: { reserved: false } }, cb); }; // expose the above method through the REST Gift.remoteMethod('listFree', { returns: { arg: 'gifts', type: 'array' }, http: { path: '/list-free', verb: 'get' } }); // method to return if the gift is free Gift.isFree = function(id, cb) { var response; Gift.find({ fields: { id: id } }, function(err, gift) { if (err) return cb(err); if (gift.reserved) response = 'Sorry, the gift is reserved'; else response = 'Great, this gift can be yours'; }); cb(null, response); }; // expose the method through REST Gift.remoteMethod('isFree', { accepts: { arg: 'id', type: 'number' }, returns: { arg: 'response', type: 'string' }, http: { path: '/free', verb: 'post' } }); };

Assim, para saber se um determinado presente está disponível, o cliente agora pode enviar uma solicitação POST para /api/Gifts/free , passando o id do presente em questão.

Ganchos Remotos

Às vezes há a necessidade de execução de algum método antes ou depois do método remoto. Você pode definir dois tipos de ganchos remotos:

  • beforeRemote() é executado antes do método remoto.
  • afterRemote() é executado após o método remoto.

Em ambos os casos, você fornece dois argumentos: uma string que corresponde ao método remoto ao qual você deseja “ligar” sua função e a função de retorno de chamada. Grande parte do poder dos ganchos remotos é que a string pode incluir curingas, portanto, é acionada por qualquer método de correspondência.

No nosso caso, vamos definir um gancho para imprimir informações no console sempre que um novo Doador for criado. Para fazer isso, vamos adicionar um gancho “antes de criar” em donor.js :

 module.exports = function(Donor) { Donor.beforeRemote('create', function(context, donor, next) { console.log('Saving new donor with name: ', context.req.body.name); next(); }); };

A solicitação é chamada com o context fornecido e o retorno de chamada next() no middleware (discutido abaixo) é chamado após a execução do gancho.

Controles de acesso

Os aplicativos LoopBack acessam dados por meio de modelos, portanto, controlar o acesso aos dados significa definir restrições nos modelos; isto é, especificando quem ou o que pode ler e gravar os dados ou executar métodos nos modelos. Os controles de acesso LoopBack são determinados por listas de controle de acesso ou ACLs.

Vamos permitir que Doadores e Destinatários não logados visualizem os presentes, mas apenas Doadores logados possam criá-los e excluí-los.

 $ slc loopback:acl

Para começar, vamos negar a todos o acesso a todos os endpoints.

 ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: All methods and properties ? Select the access type: All (match all types) ? Select the role: All users ? Select the permission to apply: Explicitly deny access

Em seguida, permita que todos leiam modelos de presentes:

 $ slc loopback:acl ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: All methods and properties ? Select the access type: Read ? Select the role: All users ? Select the permission to apply: Explicitly grant access

Em seguida, queremos permitir que usuários autenticados criem presentes:

 $ slc loopback:acl ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: A single method ? Enter the method name: create ? Select the role: Any authenticated user ? Select the permission to apply: Explicitly grant access

E, finalmente, vamos permitir que o dono do presente faça as alterações:

 $ slc loopback:acl ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: All methods and properties ? Select the access type: Write ? Select the role: The user owning the object ? Select the permission to apply: Explicitly grant access

Agora, quando gift.json , tudo deve estar no lugar:

 "acls": [ { "accessType": "*", "principalType": "ROLE", "principalId": "$everyone", "permission": "DENY" }, { "accessType": "READ", "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" }, { "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW", "property": "create" } ],

Uma observação importante aqui: $authenticated é uma função predefinida que corresponde a todos os usuários no sistema (tanto Doadores quanto Receptores), mas queremos apenas permitir que os Doadores criem novos Presentes. Portanto, precisamos de uma função personalizada. Como Role é mais uma entidade que tiramos da caixa, podemos aproveitar sua chamada de API para criar a função $authenticatedDonor na função de inicialização e, em seguida, modificar pricipalId em gift.json .

Será necessário criar um novo arquivo, server/boot/script.js , e adicionar o seguinte código:

 Role.create({ name: 'authenticatedDonor' }, function(err, role) { if (err) return debug(err); })

A entidade RoleMapping mapeia funções para usuários. Certifique-se de que Role e RoleMapping sejam expostos por meio de REST. Em server/model-config.json , verifique se "public" está definido como true para a entidade Role. Em seguida, em donor.js , podemos escrever um gancho “antes de criar” que mapeará o userID e o roleID na chamada da API POST RoleMapping.

Middleware

O middleware contém funções que são executadas quando uma solicitação é feita ao terminal REST. Como o LoopBack é baseado no Express, ele usa o middleware Express com um conceito adicional, chamado de “fases de middleware”. As fases são usadas para definir claramente a ordem na qual as funções no middleware são chamadas.

Aqui está a lista de fases predefinidas, conforme fornecido nos documentos do LoopBack:

  1. inicial - O primeiro ponto no qual o middleware pode ser executado.
  2. session - Prepara o objeto de sessão.
  3. auth - Manipula autenticação e autorização.
  4. parse - Analisa o corpo da solicitação.
  5. rotas - rotas HTTP que implementam sua lógica de aplicativo. O middleware registrado por meio da API Express app.use, app.route, app.get (e outros verbos HTTP) é executado no início desta fase. Use esta fase também para subaplicativos como loopback/server/middleware/rest ou loopback-explorer.
  6. files - Servir ativos estáticos (as solicitações estão atingindo o sistema de arquivos aqui).
  7. final - Lidar com erros e solicitações de URLs desconhecidos.

Cada fase tem três subfases. Por exemplo, as subfases da fase inicial são:

  1. inicial: antes
  2. inicial
  3. inicial: depois

Vamos dar uma olhada rápida em nosso middleware.json padrão:

 { "initial:before": { "loopback#favicon": {} }, "initial": { "compression": {}, "cors": { "params": { "origin": true, "credentials": true, "maxAge": 86400 } } }, "session": { }, "auth": { }, "parse": { }, "routes": { }, "files": { }, "final": { "loopback#urlNotFound": {} }, "final:after": { "errorhandler": {} } }

Na fase inicial, chamamos loopback.favicon() ( loopback#favicon é o id do middleware para essa chamada). Então, compression de módulos npm de terceiros e cors são chamados (com ou sem parâmetros). Na fase final, temos mais duas chamadas. urlNotFound é uma chamada LoopBack e errorhandler é um módulo de terceiros. Este exemplo deve demonstrar que muitas chamadas internas podem ser usadas como os módulos npm externos. E, claro, sempre podemos criar nosso próprio middleware e chamá-los por meio desse arquivo JSON.

loopback-boot

Para finalizar, vamos mencionar um módulo que exporta a função boot() que inicializa a aplicação. Em server/server.js , você encontrará o seguinte trecho de código, que inicializa o aplicativo:

 boot(app, __dirname, function(err) { if (err) throw err; // start the server if `$ node server.js` if (require.main === module) app.start(); });

Este script pesquisará a pasta server/boot e carregará todos os scripts que encontrar em ordem alfabética. Assim, em server/boot , podemos especificar qualquer script que deve ser executado na inicialização. Um exemplo é explorer.js , que executa o API Explorer , o cliente que usamos para testar nossa API.

Tem o blues de repetição? Não construa essa API do Node do zero novamente. Deixe o LoopBack fazer isso!
Tweet

Conclusão

Antes de deixar você, gostaria de mencionar o StrongLoop Arc, uma interface gráfica que pode ser usada como uma alternativa às ferramentas de linha de comando slc . Ele também inclui ferramentas para criar, criar perfis e monitorar aplicativos Node. Para aqueles que não são fãs da linha de comando, definitivamente vale a pena tentar. No entanto, o StrongLoop Arc está prestes a ser descontinuado e sua funcionalidade está sendo integrada ao IBM API Connect Developer Toolkit.

Conclusão

De um modo geral, o LoopBack pode economizar muito trabalho manual, pois você está obtendo muitas coisas fora da caixa. Ele permite que você se concentre em problemas específicos do aplicativo e na lógica de negócios. Se o seu aplicativo é baseado em operações CRUD e manipulação de entidades predefinidas, se você está cansado de reescrever a infraestrutura de autenticação e autorização do usuário quando muitos desenvolvedores escreveram isso antes de você, ou se você deseja aproveitar todas as vantagens de um ótimo framework web como Express, então construir sua API REST com LoopBack pode tornar seus sonhos realidade. É moleza!