Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Go From Zero to Serverless With Backand, Part 5

DZone's Guide to

How to Go From Zero to Serverless With Backand, Part 5

As we draw near the end of our series, we'll look at how to make our app fully functional and give it some security features.

· Web Dev Zone
Free Resource

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

Now that we have the majority of our application's functionality implemented, it's time to get down into the details. In Part 4 of this series, we covered adding simple creation, update, and deletion of to-do items. In this post, we'll build off of that functionality to add some dynamic display functionality, add authentication with our application, and restrict edits and updates to either the controlling user or to administrators. Once we're ready, we'll have a simple, user-restricted to-do list application with a full server component, ready to be deployed to a CDN and used by the world at large!

Adding Dynamic Behavior

Let's start by tweaking our programmer-designed user interface some. We'll notice that while we've gone to the trouble of adding a description for each to-do item in our data model, we don't actually make use of this description anywhere. Let's change this now. Currently, an individual item looks like the following:

Screen Shot 2017-04-13 at 1.05.53 PM.png

Let's remove that ugly "Expand" button, and instead make the entry itself clickable for more detail. We'll start by adding a dynamic ID to each entry as we generate the HTML in JavaScript, and we'll also add the class todo-item:

const ITEM_TAG =  "<a href='#' class='list-group-item list-group-item-action todo-item' id='item-ITEM_ID'>"
                    + "<div class='d-flex w-100 justify-content-between'>"
                    + "<i class='fa fa-times delete-item' aria-hidden='true' id='delete-ITEM_ID'></i>"
                    + "<h5 class='mb-1 col5'>ITEM_NAME</h5>"
                    + "<button type='button' class='btn btn-success'>Expand</button>"
                    + "<button type='button' class='btn btn-primary complete-button' id='complete-ITEM_ID' style='IS_COMPLETED'>Complete</button>"
                    + "<button type='button' class='btn btn-primary uncomplete-button' id='uncomplete-ITEM_ID' style='IS_NOT_COMPLETED'>Reopen</button>"
                    + "</div></a>";

The only change we've made here is to the first line, where we define the anchor tag that consists of a single item. This anchor tag gets a new class - todo-item - and a new id - item-ITEM_ID. We can use these two elements to dynamically rebuild our HTML into an "Expanded" format. We'll do this by hiding and revealing a couple tags based on user activity, and we'll leverage a couple extra classes offered by Bootstrap as well. To start with, we need to further modify our generated HTML:

 const ITEM_TAG =  "<a href='#' class='list-group-item list-group-item-action todo-item' id='item-ITEM_ID'>"
                    + "<div class='d-flex w-100 justify-content-between'>"
                    + "<i class='fa fa-times delete-item' aria-hidden='true' id='delete-ITEM_ID'></i>"
                    + "<div>"
                    + "<h5 class='mb-1 col5'>ITEM_NAME</h5>"
                    + "<br /><p class='item-description' hidden>ITEM_DESCRIPTION</p>"
                    + "</div>"
                    + "<button type='button' class='btn btn-primary btn-success complete-button' id='complete-ITEM_ID' style='IS_COMPLETED'>Complete</button>"
                    + "<button type='button' class='btn btn-primary btn-warning uncomplete-button' id='uncomplete-ITEM_ID' style='IS_NOT_COMPLETED'>Reopen</button>"
                    + "</div></a>";

We've added a new div, surrounding the item's name, and added a hidden description element with the class item-description (in addition to a couple other minor UI changes). We've also added a new dynamic value we need to update, so we'll also update our specialization code:

    itemId = itemList[i].id;
    itemName = itemList[i].name;
    itemDescription = itemList[i].description;
    isCompleted = !!itemList[i].completed;
    style = "display: none;"
    item_string = ITEM_TAG.replace(/ITEM_NAME/g, itemName);
    item_string = item_string.replace(/ITEM_ID/g, itemId);
    item_string = item_string.replace(/ITEM_DESCRIPTION/g, itemDescription);
    item_string = item_string.replace(/IS_COMPLETED/g, (isCompleted) ? style : '');
    item_string = item_string.replace(/IS_NOT_COMPLETED/g, (!isCompleted) ? style : '');
    finalHtml.push(item_string);

Notice the addition of ITEM_DESCRIPTION - this will be replaced with the item's description. The item it is added to has the "hidden" attribute, which is used to prevent the object from appearing when we don't want it to. From here, we'll now add a click handler that will drive the reveal of an item's description, once again in the document.ready handler:

  $("body").on("click", ".todo-item", function(event){
    target = event.target;
    wasActive = $(target).closest('.list-group-item').hasClass('active');
    // clear active designations
    $('.list-group-item').removeClass('active');
    $('.item-description').attr("hidden", "true")

    // If the user hasn't clicked on the same object
    if(!wasActive)
    {
      // Show the description, and highlight the item
      $(target).closest('.list-group-item').addClass('active');
      $(target).find('.item-description').removeAttr("hidden");
    }
  });


This function first checks to see whether the clicked item (or, more accurately, the enclosing list-group-item) is currently active. Then it clears all active elements from the UI and activates the clicked item if it was not previously active. This behavior results in the appearance of an instantaneous selection of an item in response to a user's click. The final result is below:


Screen Shot 2017-04-13 at 2.34.59 PM.png

Adding in Authentication

Now that we've improved our UI some, we're faced with another problem - a global to-do list is not particularly useful. Let's tie this together with a user-driven security scheme, using Backand's built-in authentication and authorization functionality. We'll start by adding a simple UI to manage the currently-authenticated user. Update index.html with the following data:

    <hr />
    <div class="form-inline" id="login-form">
      <label class="sr-only" for="usernameInput">Username</label>
      <input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="usernameInput" placeholder="Username">
      <label class="sr-only" for="password">Password</label>
      <input type="password" class="form-control mb-2 mr-sm-2 mb-sm-0" id="password" placeholder="Password">
      <div id="loginButton">
        <button  class="btn btn-primary btn-success" id="loginButton">Log In</button>
      </div>
    </div>

This creates a very simple UI for authenticating with our application, as you can see:

Screen Shot 2017-04-13 at 3.29.01 PM.png

At this point the pattern should be obvious: we update our code to add a handler for the new button, then add the SDK passthrough call to perform authentication. Let's start with the latter:

var dataService = {
  // other data service functions here...
  signin: function(username, password) {
    return backand.signin(username, password);
  }
};

Now, the click handler:

  $("body").on("click","#loginButton", function(event) {
    username = $("#usernameInput").val();
    password = $("#password").val();
    myDataService.signin(username, password);
    return false;
  });

This successfully hooks our app up for authentication, but we have a couple more items to address. First, the user interface for this is terrible - whether login succeeds or fails, we need to update the UI so that the user knows if everything completed successfully. We'll update our UI to provide a text prompt indicating that a user is logged in, and we will hide the login fields upon successful authentication. If a user is not logged in, or if login failed, we should display a warning message and log in to the application. Let's start with the code to modify a successful login. We'll add a hidden span that contains a customizable login message:

    <div id="logged-in-alert" hidden>
      <span> You are currently logged in as USER_NAME </span>
    </div>

Next, we'll add a function that dynamically updates the display to show either the login form or the "you're logged in" message. We'll call this updateLoginFormDisplay:

var updateLoginFormDisplay = function() {
  backand.user.getUserDetails().then(function(data){
    username = data.data.username;
    if(username)
    {
      template_content = $("#logged-in-message");
      template_content.html( "You are currently logged in as " + username);
      $("#logged-in-alert").removeAttr("hidden");
      $("#login-form").attr("hidden", true);
    }
    else {
      $("#logged-in-alert").attr("hidden", true);
      $("#login-form").removeAttr("hidden");
    }
  });
}

Finally, we just call our function whenever we need to update the login form display, during load and during the success handler for the login function:

  // Init the login form
  updateLoginFormDisplay();
  // other code, then in the handler for login:
  myDataService.signin(username, password).then(function(){updateLoginFormDisplay();});

With that, we're just about ready to authenticate with our application. We need just one more item - a user to authenticate as! While we can use the SDK to manage registration, to make things simple we'll just add users directly into the app using the app dashboard. In the app dashboard, under menu item Security & Auth -> Registered Users, you can use the data grid to create a new user:

Screen Shot 2017-04-13 at 4.30.30 PM.png


We're now able to successfully log into our application:

Screen Shot 2017-04-13 at 4.35.31 PM.png


Using similar functionality, we can easily implement a logout method using Backand's backand.signout( function - I'll leave that as an exercise for you to complete at your leisure.

Restricting Updates by Role

Now that we can authenticate our app's users, we'll want to start restricting their behavior so that they can only affect the items for which they are responsible. We actually have quite a bit of flexibility available via Backand's security role and security template functionality, but as security can be complex to configure correctly, I will not use that functionality for now. Let's simplify our security by working with two restrictions:

  1. All created items need to have an owner.

  2. A user can only modify items they have created.

Up until this point, we've been ignoring #1 - it was in our initial list of requirements way back in Part 2 of this series. Let's fix that now. We'll want to start by preventing items from being created when a user has not logged in. To do so, let's just modify our updateLoginFormDisplay function to hide or show the create form as needed:

var updateLoginFormDisplay = function() {
  backand.user.getUserDetails().then(function(data){
    username = data.data.username;
    if(username)
    {
      template_content = $("#logged-in-message");
      template_content.html( "You are currently logged in as " + username);
      $("#logged-in-alert").removeAttr("hidden");
      $("#create-form").removeAttr("hidden");
      $("#login-form").attr("hidden", true);
    }
    else {
      $("#logged-in-alert").attr("hidden", true);
      $("#create-form").attr("hidden", true);
      $("#login-form").removeAttr("hidden");
    }
  });
}

We also want to add an id to our creation form, as follows:

<form class="form-inline" id="create-form">

Now, we only show the login form when the user has authenticated. There is one minor caveat we need to be aware of, though. If you'll recall, we've configured our application to use Anonymous Authentication. The way this is handled by the getUserDetails method is to return a "guest" user, with a null ID. As our login function keys off the presence of the User object and detects the login based only on the username, we'll need to expand our function to instead check the user's ID for a value:

var updateLoginFormDisplay = function() {
  backand.user.getUserDetails().then(function(data){
    username = data.data.username;
    user_id = data.data.userId;
    if(user_id)
    {
      template_content = $("#logged-in-message");
      template_content.html( "You are currently logged in as " + username);
      $("#logged-in-alert").removeAttr("hidden");
      $("#create-form").removeAttr("hidden");
      $("#login-form").attr("hidden", true);
    }
    else {
      $("#logged-in-alert").attr("hidden", true);
      $("#create-form").attr("hidden", true);
      $("#login-form").removeAttr("hidden");
    }
  });
}

And with that, we now correctly hide and show the item creation form based on the user's authentication status. Next, we'll need to update our item creation based on the authenticated user. We'll wrap the item creation call in a call to getUserDetails, and use the value of the userId to perform the restriction:

  // Define the click handler
  $("#createItem").click(function(){
    // get data from the form
    objectData = {}
    nameField = $("#nameInput");
    descriptionField = $("#descriptionInput");
    objectData.name = nameField.val();
    objectData.description = descriptionField.val();
    // reset the form
    nameField.val('');
    descriptionField.val('');

    backand.user.getUserDetails().then(function(userData){
      objectData.owner = userData.data.userId;
      if(objectData.owner)
      {
        // create the item, then reload the view
        myDataService.create(objectData).then(function(){myDataService.getData();});
      }
      else {
        alert("You must be logged in to create an item.");
      }
    });
    // return false to stop propagation
    return false;
  });

The key component here is the call to getUserDetails. When this succeeds, we fetch the user ID from the user object and provide this as the "owner" property of our to-do items. By providing this ID as the "owner," Backand will automatically detect the linkage between our user's table and our to-do items and will return the correct user data when we fetch the item from the server.

Now that we have ensured that every to-do item has an owner, we can implement the update and deletion restrictions using custom actions in Backand. Open up the "items" object from the Backand dashboard, and select the "actions" tab:

Screen Shot 2017-04-13 at 5.17.45 PM.png


Custom actions are server-side code that can be used to manipulate database records at any point in the database transaction, whether that call is performing a creation, update, or a deletion. You can also create actions that execute on-demand, in response to hitting a specific URL. To implement a restriction preventing a user from editing or deleting items they don't own, we'll create two actions - one before Update, and one before Delete. In both cases, the custom action code will be exactly the same:

'use strict';
function backandCallback(userInput, dbRow, parameters, userProfile) {
  // write your code here
  if(userProfile.userId != dbRow.owner)
  {
    throw new Error("The user is not authorized to update this object");
  }
  return {};
}

This simply checks the owner's ID against the userId of the currently authenticated user and rejects the call if the values do not match. Now, if you reload your page and try to update an item that you don't know, the call will fail.

Conclusion

With the above, we've officially completed our to-do list application. We've created an app that allows a user to log in, to create to-do items, to update their completion status, and to delete items from the database. We've also added simple security restrictions on objects, restricting the creation to only authenticated users, and restricting updates and deletes to only be performed by an item's owners. At this point, where you take this application is up to you. We've demonstrated all of the major components of an application, from CRUD functionality all the way through to server-side code execution, all without building and deploying a server to drive our database and back-end. From here, you can deploy your code using Backand's CDN, or modify your code to add new features, or even begin working with a brand new model - the world is literally at your fingertips!

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

Topics:
jquery ,javascript ,serverless frameworks ,web dev

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}