Usar o Cloud Datastore com Node.js

Nesta parte do tutorial do Bookshelf em Node.js, há informações sobre como o app de amostra armazena dados permanentes no Google Cloud Datastore. Na amostra de código desta etapa, são fornecidos exemplos de como criar, ler, atualizar e excluir (CRUD, na sigla em inglês) de dados armazenados no Cloud Datastore.

Esta página é parte de um tutorial com várias páginas. Para começar do início e ler as instruções de configuração, acesse o app Node.js Bookshelf.

Como definir configurações

No diretório nodejs-getting-started/2-structured-data, crie um arquivo config.json com este conteúdo:

{
  "GCLOUD_PROJECT": "[YOUR_PROJECT_ID]",
  "DATA_BACKEND": "datastore"
}

Substitua [YOUR_PROJECT_ID] pelo código do projeto.

O Cloud Datastore é um serviço totalmente gerenciado iniciado automaticamente e conectado ao seu aplicativo do App Engine. Ele não requer nenhuma outra configuração.

Instalar dependências

Use npm para instalar as dependências no diretório nodejs-getting-started/2-structured-data:

    npm install

Execução do app na máquina local

  1. Inicie um servidor da Web local usando npm:

        npm start
    
  1. No navegador da Web, digite este endereço:

    http://localhost:8080

Agora navegue nas páginas da Web do app e adicione, edite e exclua livros.

Como implantar o app no ambiente padrão do App Engine

  1. Implante o aplicativo de amostra a partir do diretório nodejs-getting-started/2-structured-data:

    gcloud app deploy
    
  2. No navegador da Web, digite este endereço. Substitua [YOUR_PROJECT_ID] pelo seu código do projeto:

    https://[YOUR_PROJECT_ID].appspot.com
    

Para atualizar o app, implante a versão atualizada digitando o mesmo comando usado para implantá-lo pela primeira vez. A implantação cria uma nova versão do aplicativo e a define como padrão. As versões anteriores do seu app são mantidas. Por padrão, o ambiente do App Engine é escalonado para zero instâncias quando não há tráfego de entrada em uma versão. Portanto, as versões não usadas não serão cobradas. No entanto, todas essas versões de apps ainda são recursos faturáveis.

Consulte a seção Limpeza na etapa final deste tutorial para ver mais informações sobre a limpeza de recursos faturáveis, incluindo versões de apps não padrão.

Como entender o código

Nesta seção, vamos analisar o código do aplicativo e explicar como ele funciona.

Como processar envios de usuário com formulários

Com o formulário de adição/edição, os usuários conseguem adicionar e editar envios de livro no aplicativo.

Imagem do formulário de adição/edição

O formulário HTML é criado usando Pug, que é um mecanismo de modelo do Node.js. O seguinte modelo Pug especifica que o formulário tem campos de entrada de texto para "Title", "Author", "Date Published" e "Description":

block content
  h3 #{action} book
  form(method="POST")
    .form-group
      label(for="title") Title
      input.form-control(type="text", name="title", id="title", value=book.title)
    .form-group
      label(for="author") Author
      input.form-control(type="text", name="author", id="author", value=book.author)
    .form-group
      label(for="publishedDate") Date Published
      input.form-control(type="text", name="publishedDate", id="publishedDate", value=book.publishedDate)
    .form-group
      label(for="description") Description
      input.form-control(type="text", name="description", id="description", value=book.description)
    .form-group
    button.btn.btn-success(type="submit") Save

Processar envios de formulário

Quando um usuário clica em Adicionar livro, o seguinte código em books/crud.js o direciona para a página /add do aplicativo, que exibe o formulário de adição/edição:

router.get('/add', (req, res) => {
  res.render('books/form.pug', {
    book: {},
    action: 'Add'
  });
});

Quando o usuário preenche o formulário Adicionar livro e clica em Salvar, a ação HTTP POST do formulário é processada pelo código a seguir em books/crud.js. Isso inicia o processo de envio dos dados coletados ao Cloud Datastore transferindo-os para a função model.create.

router.post('/add', (req, res, next) => {
  const data = req.body;

  // Save the data to the database.
  getModel().create(data, (err, savedData) => {
    if (err) {
      next(err);
      return;
    }
    res.redirect(`${req.baseUrl}/${savedData.id}`);
  });
});

O arquivo books/model-datastore.js contém o código que executa as funções de criação, leitura, atualização e exclusão (CRUD, na sigla em inglês) dos dados armazenados no Cloud Datastore. Por exemplo, a instrução model.create chama a função create em model-datastore.js, que usa os dados enviados pelo usuário para criar um novo envio de livro.

module.exports = {
  create,
  read,
  update,
  delete: _delete,
  list
};

Essa é a função de update que salva os dados enviados pelo usuário no Cloud Datastore. As funções auxiliares toDatastore e fromDatastore são usadas para converter os dados enviados pelo usuário do formato do app para o formato da propriedade de entidade estendida do Cloud Datastore.

function update (id, data, cb) {
  let key;
  if (id) {
    key = ds.key([kind, parseInt(id, 10)]);
  } else {
    key = ds.key(kind);
  }

  const entity = {
    key: key,
    data: toDatastore(data, ['description'])
  };

  ds.save(
    entity,
    (err) => {
      data.id = entity.key.id;
      cb(err, err ? null : data);
    }
  );
}

Depois que os usuários tiverem adicionado os livros, basta clicar no link Livros para navegar pela página /books, que lista todos os livros armazenados no Cloud Datastore. A página usa o arquivo books/model-datastore.js, que inicializa um cliente do Cloud Datastore chamado ds e define a variável de kind como Book, que é o tipo de entidade que o modelo gerencia.

const ds = Datastore({
  projectId: config.get('GCLOUD_PROJECT')
});
const kind = 'Book';

Em seguida, a função list lista todos os livros usando dados recuperados do Cloud Datastore. A instrução const q = ds.createQuery([kind]) cria uma consulta chamada q, que seleciona todos os livros do armazenamento de dados. As seguintes propriedades extras são adicionadas à consulta:

  • .limit(limit) define a quantidade máxima de resultados retornados por página.
  • .order('title') especifica que os resultados serão exibidos por título de livro, em ordem alfabética.
  • .start(token) especifica o ponto de início do processamento da paginação e permite que outras páginas sejam solicitadas com base no valor do token.

Para executar a consulta, ds.runQuery é chamado, e o objeto q é usado como consulta a ser executada para o primeiro parâmetro. O segundo parâmetro do ds.runQuery é um callback invocado com os argumentos (err, entities, nextQuery). Por fim, o callback da função de list cb é chamado com os argumentos (err, books, nextPageToken).

function list (limit, token, cb) {
  const q = ds.createQuery([kind])
    .limit(limit)
    .order('title')
    .start(token);

  ds.runQuery(q, (err, entities, nextQuery) => {
    if (err) {
      cb(err);
      return;
    }
    const hasMore = nextQuery.moreResults !== Datastore.NO_MORE_RESULTS ? nextQuery.endCursor : false;
    cb(null, entities.map(fromDatastore), hasMore);
  });
}