https://a.storyblok.com/f/270183/96814/de61818b4a/appointment-scheduler.png

Crie um agendador de consultas usando Node, Firebase e Vonage

Publicado em December 16, 2021

Tempo de leitura: 12 minutos

Introdução

Neste tutorial, vamos criar um aplicativo web para agendamento de compromissos usando Node.js, Express, Firebase e a Messages API for Vonage. O repositório do GitHub para este projeto também está disponível; fique à vontade para cloná-lo aqui.

Configurar o Firebase

Para começar, vamos criar um novo projeto a partir do console do Firebase.

  • Clique em add a new project

  • Dê ao seu projeto um nome significativo, por exemplo vonage appointment scheduler

  • Verifique se você está satisfeito com o identificador único (id) do seu projeto (ele é usado na URL do seu Realtime Database, nos subdomínios do Firebase Hosting e em outros lugares. Ele não pode ser alterado após a criação do projeto)

  • Clique no botão para continuar

Console view with a text field to enter project and name and edit the project idConsole view with a text field to enter project and name and edit the project id

  • Selecione essa opção se quiser ativar o sistema de análise. Neste tutorial, não vamos fazer isso

  • Clique no botão para criar o projeto

  • Aguarde até que o projeto seja criado

Project being createdProject being created

  • Quando o projeto estiver pronto, clique para continuar. Você será direcionado para a visualização do console do seu projeto

  • Defina o tipo de cobrança clicando no ícone de engrenagem, depois em “Uso e cobrança” e, em seguida, na guia “Detalhes e configurações” para alterar o plano para o Blaze. Esse plano pré-pago é obrigatório ao usar uma API de terceiros

Instalar a CLI do Firebase Tools

No seu terminal, instale as ferramentas do Firebase com o NPM, caso ainda não as tenha, digitando: npm install -g firebase-tools. Em seguida, digite firebase login. Isso abrirá uma janela no seu navegador que irá autenticá-lo automaticamente (se você já estiver conectado) ou solicitará suas credenciais. Assim que isso for concluído, você terá a CLI do Firebase instalada.

Criar e configurar um banco de dados RealTime

Agora é hora de criarmos a instância do banco de dados NoSQL que armazenará as informações dos horários disponíveis para agendamento. Nosso aplicativo incluirá uma tela na qual o usuário poderá marcar ou cancelar agendamentos. À medida que a pessoa que estiver interagindo com a tela escolher uma data e um horário para o agendamento, esse horário será adicionado ou removido do Firebase RealTime Database.

  • No menu do Console do Firebase, clique em “Realtime Database” na seção “Build”

Button to create the databaseButton to create the database

  • Clique em “Criar banco de dados”

  • Selecione o local do Realtime Database onde seus dados serão armazenados e clique em next

  • Selecione se você usará o banco de dados no modo bloqueado ou de teste. Neste exemplo, estou usando o modo de teste

  • Clique enable

    Database createdDatabase created

Importar o arquivo JSON do banco de dados

Vamos importar um banco de dados de exemplo que já contém alguns slots alocados e no qual você poderá adicionar e remover slots no futuro. Você pode criar um arquivo chamado myAppointments.json contendo o JSON do trecho abaixo e, em seguida, importar esse arquivo pelo console.

myAppointments.json
{
  "myAppointments": {
    "0": {
      "date": "2021-06-01T09:00",
      "userId": "1234abcd"
    },
    "new_activity_7kh3a3a3z": {
      "date": "2023-06-01T08:50",
      "userId": "_7kh3a3a3z"
    },
    "new_activity_etxen95x3": {
      "date": "2021-06-01T08:40",
      "userId": "_etxen95x3"
    }
  }
}

Import DatabaseImport Database

Adicionar as regras do banco de dados

As regras do Firebase Realtime Database determinam quem pode acessar seu banco de dados, como seus índices são criados e como seus dados são estruturados.

  • No console do Firebase, na visualização do banco de dados em tempo real, você verá a opção “Regras”; clique nessa aba. Você será direcionado para uma tela que permitirá editar suas regras

  • Copie e cole as regras do trecho de código abaixo no seu console para definir a myAppointments coleção a ser indexada pelo date campo.

  • Clique em Publish

{
  "rules": {
    ".read": "now < 1643842800000",  // 2022-2-3
    ".write": "now < 1643842800000",  // 2022-2-3
    "myAppointments": {
      ".indexOn": ["date"]
    }
  }
}

Edit Firebase Database RulesEdit Firebase Database Rules

Criar a estrutura do projeto

Ao final deste tutorial, a estrutura do seu projeto ficará mais ou menos assim. Nas etapas a seguir, criaremos os arquivos que compõem o conteúdo, a aparência e as funcionalidades, além de gerenciar os serviços que utilizaremos.

Project Structure also displayed as a code block below for accessibilityProject Structure

appointment-scheduler | public | |- styles | | | L styles.css | |- favicon.ico | L index.html |- script | |- server.js |- .env |- .firebaserc |- README.md |- firebase.json |- package-lock.json |- package.json |- serviceAccountKey.json

Configuração

  • Crie a pasta do projeto e cd nela: mkdir appointment-scheduler && cd appointment-scheduler

  • Inicializar o NPM: npm init. Esse comando solicita que você insira informações sobre o projeto

  • Instale as dependências: npm install @vonage/server-sdk dotenv uuid express firebase-admin firebase-functions

  • Tipo firebase init. Como já criamos um projeto no painel, você pode selecionar Use an existing project , o que solicitará que você escolha o projeto desejado. Você pode ver meu exemplo com o ID do meu projeto vonage-appointment-scheduler abaixo. Também optei por usar o Realtime Database recurso

? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance === Project Setup First, let's associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now, we'll just set up a default project. ? Please select an option: Use an existing project ? Select a default Firebase project for this directory: vonage-appointment-scheduler (vonage appointment scheduler) i Using project vonage-appointment-scheduler (vonage appointment scheduler)

Criar o conteúdo HTML

Você sabia que o elemento `input` do HTML oferece várias opções de tipo para a seleção de data e hora? Por exemplo, temos: date, datetime-local, time. Para este tutorial, usaremos <input type="datetime-local">. Essa abordagem talvez não seja tão robusta quanto usar uma biblioteca de data e hora, pois pode haver algumas inconsistências, mas funciona para os fins deste tutorial. O usuário poderá agendar horários a cada 5 minutos que terminem em 0 ou 5; por exemplo, 18h00 está disponível para agendamento, mas 18h01 não está.

  • Crie a public/index.html que contenha o conteúdo da visualização para selecionar um novo compromisso ou cancelá-lo, adicionando o trecho de código abaixo

  <!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Appointment Scheduler</title>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="styles/styles.css" />
  </head>
  <body>
    <main>
      <h1>Appointment Scheduler</h1>
      <!-- datepicker from html. Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local -->
      <form action="/appointment" method="POST">
        <div>
          <label for="slot">Choose your slot: </label>
          <input
            id="slot"
            type="datetime-local"
            name="slotdate"
            min="2021-06-01T08:30"
            max="2023-10-30T16:30"
            step="300"
            required
          />
          <span class="validity"></span>
        </div>
        <div>
          <label for="phonenumber">Your phone number:</label>
          <input type="tel" id="phonenumber" name="phonenumber" required />
          <span class="validity"></span>
        </div>
        <div>
          <input type="submit" value="Book slot!" />
        </div>
      </form>
      <form action="/cancelAppointment" method="POST">
        <div>
          <input type="text" name="code" placeholder="code" />
          <input type="submit" value="Remove slot!" />
        </div>
      </form>
    </main>
  </body>
</html>

Adicionar estilos CSS

Para este aplicativo web de demonstração, vamos aplicar alguns estilos para centralizar o conteúdo na página e também exibir um ✖ vermelho caso a entrada seja inválida e um ✓ caso seja válida.

  • Crie o public/styles.css arquivo

  • Cole o código CSS abaixo

body {
    margin: auto;
    width: 50%;
    padding: 10px;
}

div {
    margin-bottom: 10px;
    display: flex;
    align-items: center;
}

label {
  display: inline-block;
  width: 300px;
}

input:invalid+span:after {
    content: '✖';
    color: red;
    padding-left: 5px;
}

input:valid+span:after {
    content: '✓';
    color: green;
    padding-left: 5px;
}

Criar o arquivo de variáveis de ambiente

  • Crie o .env e preencha-o com as informações abaixo

FIREBASE_DATABASE_URL= VONAGE_API_KEY= VONAGE_API_SECRET= VONAGE_FROM_NUMBER= VONAGE_TO_NUMBER=
  • O FIREBASE_DATABASE_URL pode ser encontrado no console do Firebase

  • O VONAGE_API_KEYe o VONAGE_API_SECRET podem ser encontrados no Painel da Vonage

  • O VONAGE_FROM_NUMBER contém o número, o nome ou a marca que aparecerá como remetente da mensagem

  • O VONAGE_TO_NUMBER é o número que receberá as mensagens SMS

Crie o arquivo JavaScript server.js

Vamos criar o server.js para informar ao Express como lidar com as solicitações enviadas pela interface do usuário. Vou mostrar passo a passo como vamos construí-lo. Você pode encontrar o arquivo completo do servidor aqui.

Nosso aplicativo web utilizará o Express e lerá os arquivos estáticos que criamos anteriormente na public pasta.

  • Para adicionar as dependências e os arquivos de importação, insira o trecho de código abaixo no seu script/server.js

// script/server.js
require('dotenv').config();
const express = require('express');
const app = require('express')();
const port = 3000; //setting the port to listen to as 3000
const admin = require('firebase-admin');
const Vonage = require('@vonage/server-sdk');
const SMS = require('@vonage/server-sdk/lib/Messages/SMS';
const { v4: uuidv4 } = require('uuid');

app.use(express.static('public'));

app.use(express.json());

app.use(express.urlencoded({ extended: true }));

Adicionar a conta de serviço

Uma conta de serviço do Firebase pode ser usada para autenticar diversos recursos do Firebase; para o nosso projeto, usaremos o SDK de administração do Firebase para acessar a URL do nosso banco de dados.

  • No Console do Firebase, clique na engrenagem e selecione a aba “Conta de serviço”

  • Clique no botão para generate key

  • Adicione o arquivo gerado à raiz do seu projeto e renomeie-o para serviceAccountKey.json

  • Copie e cole o trecho de código de configuração do Admin SDK no seu projeto, conforme mostrado na etapa seguinte deste tutorial, para inicializar o Firebase. Estamos usando ${process.env.FIREBASE_DATABASE_URL para ler a URL do .env arquivo, mas é a mesma URL do banco de dados encontrada na configuração do Admin SDK do Firebase.

Admin SDK configurationAdmin SDK configuration

Inicializar o Firebase

Usamos initializeApp para criar e inicializar uma instância do aplicativo Firebase que utilizará a /myAppointments instância do banco de dados do Firebase que criamos e preenchemos anteriormente no Console do Firebase.

  • Adicione este trecho de código abaixo ao seu server.js para inicializar o Firebase.

const serviceAccount = require('../serviceAccountKey.json');

// Initializes firebase
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: `${process.env.FIREBASE_DATABASE_URL}`,
});

// A Reference represents a specific location in your Database and can be 
// used for reading or writing data to that Database location.
ref = admin.database().ref('/myAppointments');

Inicializar o objeto da API da Vonage

Criamos a instância da classe cliente da Vonage, inicializando-a com a chave e o segredo da API da Vonage que você adicionou anteriormente ao seu .env arquivo.

  • Adicione o trecho de código abaixo ao seu server.js para adicionar o Vonage.

const vonage = new Vonage({
  apiKey: process.env.VONAGE_API_KEY,
  apiSecret: process.env.VONAGE_API_SECRET,
});

Criar a função getDateTime()

O tipo de entrada HTML datetime-local é formatado como AAAA-MM-DDThh:mm. Portanto, vamos escrever uma função para separar a data da hora, dividindo-a no caractere T. Por exemplo, no exemplo 2018-06-12T19:30, teríamos 2018-06-12 para a data e 19:30 para a hora.

  • Adicione este trecho de código abaixo ao seu server.js para adicionar a getDateTime() função

  const getDateTime = (slot) => {
    return slot.split('T');
  };

Crie o /appointment ponto final

É hora de criar o /appointment endpoint para lidar com as solicitações POST de criação de um compromisso. Esse endpoint verificará se o horário está disponível, adicionará o horário ao banco de dados do Firebase e, por fim, enviará uma confirmação por SMS para o celular do usuário usando a Messages API do Vonage.

  • Adicione este trecho de código abaixo ao seu server.js para criar o /appointment ponto de extremidade.

app.post('/appointment', async (request, response) => {
  let phonenumber = request.body.phonenumber;
  let slot = request.body.slotdate;
  let [date, time] = getDateTime(slot);

  // Checks if a slot is available
  checkIfAvailable = async (slot) => {};
  
  // Adds to Database
  addToDatabase = () =>  {};
  
  // Sends an SMS back to the user's phone using the Vonage Messages API
  sendSMStoUser = async (code) => {};
});

Você deve ter percebido que grande parte da funcionalidade dentro do manipulador de solicitações ainda não foi implementada; portanto, vamos agora expandir os esboços das funcionalidades necessárias.

Verificar a disponibilidade de vagas

Essa função verifica se um slot está disponível, verificando se ele já existe no banco de dados. Estamos consultando ref.orderByChild('date'). As consultas podem ordenar uma chave por vez. Definimos previamente nosso índice por meio das .indexOn nas Regras do Firebase para obter melhor desempenho. Em seguida, utilizamos .once('value') para escutar exatamente um evento com o valor e, em seguida, interrompe a escuta.

  • Adicione este trecho de código abaixo ao seu server.js para criar a checkIfAvailable() função

  // Checks if a slot is available
  checkIfAvailable = async (slot) => {
    let snapshot = await ref.orderByChild('date').once('value');

    let available = true;
    
    snapshot.forEach((data) => {
      let dataval = data.val();
      for (let key in dataval) {
        let datapoint = dataval[key];
        if (slot === datapoint) {
          available = false;
        }
      }
    });
    return available;
  };

Adicionar o slot ao banco de dados

A função a seguir addToDatabase() adiciona o horário e um código ao banco de dados do Firebase. Esse código é necessário para cancelar o agendamento.

  // Adds the slot to the database
  addToDatabase = () => {
    let code = uuidv4();

    ref.child(code).set({
      date: slot,
      userId: code,
    });

    return code;
  };

Envie um SMS com as informações da consulta

Por fim, assim que o horário for reservado, é enviada uma mensagem de SMS de confirmação ao usuário com o seguinte texto Meeting booked at ${time} on date: ${date}. Please save this code: ${code} in case you'd like to cancel your appointment. como você pode ver na função sendSMStoUser().

  • Adicione este trecho de código abaixo ao seu server.js para criar a sendSMStoUser() função

// Sends an SMS back to the user's phone using the Vonage Messages API
sendSMStoUser = async (code) => {
  const to = phonenumber;
  const text = `Meeting booked at ${time} on date: ${date}. Please save this code: ${code} in case you'd like to cancel your appointment.`;
  const result = await new Promise((resolve, reject) => {
    vonage.messages.send(
      new SMS(text, process.env.VONAGE_TO_NUMBER, "Vonage"),
      (err, data) => {
        if (err) {
          console.error(err);
        } else {
          console.log(data.message_uuid);
        }
      }
    );
  });
};

Finalizar a lógica de negócios

O trecho de código abaixo é responsável por chamar as funções auxiliares criadas anteriormente. Se o horário estiver disponível, o horário do usuário será adicionado ao banco de dados e ele receberá um SMS de confirmação. Caso contrário, será solicitado que ele escolha um horário diferente.

let available = await checkIfAvailable(slot);

if (available) {
	let code = addToDatabase();
	await sendSMStoUser(code);
	response.send(`This slot is available, booking it for you now: ${slot}`);
} else {
	// Sends user error
	response.send(
		`Sorry, you'll need to choose a different slot.${slot} is already busy.`
	);
}

Cancelar a consulta /cancelAppointment

Vamos criar o /cancelAppointment endpoint que lida com as solicitações POST para cancelar um agendamento do banco de dados, usando um código fornecido pelo usuário, que ele recebeu ao agendar o compromisso.

app.post('/cancelAppointment', async (request, response) => {
  let code = request.body.code;

  // Removes slot from the database
  removeSlotFromDB = (code) => {
    ref.child(code).remove();
  };
  removeSlotFromDB(code);

  response.send(`This slot has been removed.`);
});

Ouça o Porto

Por fim, o aplicativo ficará à escuta na porta especificada; se for executado localmente, ele estará acessível em https://localhost:${port}. Nessa URL, você pode interagir com a interface do usuário deste aplicativo de demonstração e verificar os slots que estão sendo adicionados ou removidos na página da web do console do Firebase.

app.listen(port, () => {
  console.log(`I run on port ${port}`);
});

Experimente

  • No seu package.json arquivo, adicione o script de inicialização "start": "node script/server.js" logo abaixo de "test": "echo \"Error: no test specified\" && exit 1",. Deve ficar assim:

"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node script/server.js" },
  • Instale todas as dependências npm install

  • Execute o comando NPM para executar o projeto npm run start

  • Acesse http://localhost:3000

  • Adicione e remova horários de agendamento e veja como eles são adicionados e removidos do Banco de Dados em Tempo Real do Firebase

Example adding a slot and it is shown on the Firebase Realtime databaseExample adding a slot and it being shown on the Firebase Realtime database

Conclusão e próximos passos

Hoje você viu como criar um aplicativo web de demonstração para agendamento de consultas. Agora você pode seguir em frente e adicionar um estilo mais sofisticado e outras funcionalidades. Você pode usar o que aprendeu aqui para criar diversos sistemas de agendamento, seja para uma academia ou para agendar horários de vacinação — deixe a criatividade fluir!

Entre em contato conosco no Twitter e junte-se à nossa comunidade no Slack.

Compartilhar:

https://a.storyblok.com/f/270183/400x400/3f6b0c045f/amanda-cavallaro.png
Amanda CavallaroRepresentante de Desenvolvedores