Userbase

Docs : Quickstart

In this tutorial we will build a very basic to-do web app. You can think of this tutorial as a demonstration of the core functionality of Userbase in the simplest way possible. We are going to focus solely on building a functional web app. Making things pretty is left as an exercise to the reader.

The entire web app we'll be building will fit in a single static HTML file of 182 lines. You can also see a live demo of the final result.

Setting up

Let's get going. Open a new file in your favorite code editor.

    
      code ugly-todo.html
    
  

And add some boilerplate HTML.

      
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <title>Ugliest To-Do</title>
        </head>

        <body>
          <!-- application code -->
          <script type="text/javascript">
          </script>
        </body>
        </html>
      
    

Now, open this file in a web browser of your choosing. At this point all you'll see is a blank page. As we add functionality throughout the tutorial, you can refresh this page to see the changes.

Creating an admin account

To complete this tutorial, you'll need to create a Userbase admin account. Upon creation, a default application named "Starter App" will be created. Take note of the App ID because we'll need it very soon.

Installing the SDK

We're going to load the Userbase SDK from a CDN with a <script> tag in the head of our page.

    
  

The Userbase SDK will now be accessible via the userbase variable. This will be our only dependency.

Initializing the SDK

Before doing anything with the Userbase SDK, we need to let it know our App ID. Simply replace 'YOUR_APP_ID' with the App ID you received when you created your admin account.

    
    <body>
      <!-- application code -->
      <script type="text/javascript">
        userbase.init({ appId: 'YOUR_APP_ID' })
      </script>
    </body>
    </html>
    
  

Letting new users create an account

Before our users can start creating to-dos, we need to give them a way to create an account with our app.

First, let's add a sign up form.

      
      <body>
        <!-- Auth View -->
        <div id="auth-view">
          <h1>Create an account</h1>
          <form id="signup-form">
            <input id="signup-username" type="text" required placeholder="Username">
            <input id="signup-password" type="password" required placeholder="Password">
            <input type="submit" value="Create an account">
          </form>
          <div id="signup-error"></div>
        </div>

        <!-- application code -->
        <script type="text/javascript"></script>
      </body>
    
    

Then, let's add the code to handle the form submission.

      
      <!-- application code -->
      <script type="text/javascript">
        userbase.init({ appId: 'YOUR_APP_ID' })

        function handleSignUp(e) {
          e.preventDefault()
  
          const username = document.getElementById('signup-username').value
          const password = document.getElementById('signup-password').value
  
          userbase.signUp({ username, password, rememberMe: 'none' })
            .then((user) => alert('You signed up!'))
            .catch((e) => document.getElementById('signup-error').innerHTML = e)
        }
  
        document.getElementById('signup-form').addEventListener('submit', handleSignUp)
      </script>
      
    

Now, whenever someone submits the form, the handleSignUp function will be called. This gets the values of the username and password inputs and calls userbase.signUp({ username, password, rememberMe: 'none' }) to create a new user account with Userbase. We are specifying rememberMe: 'none' because we won't implement automatic login until the end of the guide.

Go ahead and reload the page in your browser. Enter a username and password in the form and submit. You should get an alert saying that you signed up. And if you go to your Userbase admin account, you should also see the new user under your app.

Now try signing up for another account using the same username and you'll see an error message displayed under the form.

We'll come back to this function in a bit to make it do something more interesting.

Letting users log in

Now that our users can create accounts, let's give them the ability to login.

First, let's add a "Login" form to the page above our "Create Account" form.

    
    <body>
      <!-- Auth View -->
      <div id="auth-view">
        <h1>Login</h1>
        <form id="login-form"> 
          <input id="login-username" type="text" required placeholder="Username">
          <input id="login-password" type="password" required placeholder="Password">
          <input type="submit" value="Sign in">
        </form>
        <div id="login-error"></div>

        <h1>Create an account</h1>
        <form id="signup-form">
    
  

Then, let's add the code to handle the form submission.

    
    <!-- application code -->
    <script type="text/javascript">
      userbase.init({ appId: 'YOUR_APP_ID' })

      function handleLogin(e) {
        e.preventDefault()

        const username = document.getElementById('login-username').value
        const password = document.getElementById('login-password').value

        userbase.signIn({ username, password, rememberMe: 'none' })
          .then((user) => alert('You signed in!'))
          .catch((e) => document.getElementById('login-error').innerHTML = e)
      }

      function handleSignUp(e) {
        e.preventDefault()

    …

    </script>

    
  

And finally, let's bind our login form with our login handler.

    
    <!-- application code -->
    <script type="text/javascript">

    …

        .catch((e) => document.getElementById('signup-error').innerHTML = e)
      }

      document.getElementById('login-form').addEventListener('submit', handleLogin)
      document.getElementById('signup-form').addEventListener('submit', handleSignUp)
    </script>
    </body>
    
  

You'll notice that this looks very similar to the sign up code from before. The handleLogin function gets the values of the username and password inputs, and calls userbase.signIn({ username, password, rememberMe: 'none' }). This will attempt to sign in the user, handling a success with an alert and a failure by displaying the error.

Reload the page and you should see our new login form. Enter the username and password you used to create an account in the previous step, and submit the form. You should get an alert saying that you signed in.

Try submitting the form again with incorrect credentials and you'll see an error message displayed under the form.

Showing the to-do view

After a user signs in, we'll want to hide the authentication forms and display their to-do list. First, let's add a container for the to-do list under the authentication forms.

    

    <!-- Auth View -->
    <div id="auth-view">

    …

    </div>

    <!-- To-dos View -->
    <div id="todo-view">
      <div id="username"></div>

      <h1>To-Do List</h1>
      <div id="todos"></div>
    </div>

    <!-- application code -->
    <script type="text/javascript">
      userbase.init({ appId: 'YOUR_APP_ID' })

    …

    </script>
    
  

Then, let's make this view hidden by default, and add a function to display it.

      
      <!-- application code -->
      <script type="text/javascript">

      …

          .catch((e) => document.getElementById('signup-error').innerHTML = e)
        }

        function showTodos(username) {
          document.getElementById('auth-view').style.display = 'none'
          document.getElementById('todo-view').style.display = 'block'
          document.getElementById('username').innerHTML = username
        }
        
        document.getElementById('login-form').addEventListener('submit', handleLogin)
        document.getElementById('signup-form').addEventListener('submit', handleSignUp)
      
        document.getElementById('todo-view').style.display = 'none'
      </script>
      
    

Now that we have a function to show a view for signed in users, let's change handleLogin to call this function instead of showing an alert.

    
    function handleLogin(e) {
      e.preventDefault()

      const username = document.getElementById('login-username').value
      const password = document.getElementById('login-password').value

      userbase.signIn({ username, password, rememberMe: 'none' })
        .then((user) => showTodos(user.username))
        .catch((e) => document.getElementById('login-error').innerHTML = e)
    }
    
  

And we do the same thing for handleSignUp.

    
    function handleSignUp(e) {
      e.preventDefault()

      const password = document.getElementById('signup-password').value

      userbase.signUp({ username, password, rememberMe: 'none' })
        .then((user) => showTodos(user.username))
        .catch((e) => document.getElementById('signup-error').innerHTML = e)
    }
    
  

Reload the page and login again using your username and password. You should see the authentication view disappear and your username show up along with the to-do list heading.

Using the database

Every time a user signs in, we need to establish a connection with the database that will hold the user's to-dos.

First, let's add a couple of elements for showing a loading indicator and error messages.

    
  

Then, let's change showTodos to open a new database connection.

    
    function showTodos(username) {
      document.getElementById('auth-view').style.display = 'none'
      document.getElementById('todo-view').style.display = 'block'

      // reset the todos view
      document.getElementById('username').innerHTML = username
      document.getElementById('todos').innerText = ''
      document.getElementById('db-loading').style.display = 'block'
      document.getElementById('db-error').innerText = ''

      userbase.openDatabase({ databaseName: 'todos', changeHandler })
        .catch((e) => document.getElementById('db-error').innerText = e)
    }

    function changeHandler(items) {
      document.getElementById('db-loading').style.display = 'none'

      const todosList = document.getElementById('todos')

      if (items.length === 0) {
        todosList.innerText = 'Empty'
      } else {
        // render to-dos, not yet implemented
      }
    }

    document.getElementById('login-form').addEventListener('submit', handleLogin)
    document.getElementById('signup-form').addEventListener('submit', handleSignUp)
    
  

We changed showTodos to make a call to userbase.openDatabase({ databaseName: 'todos', changeHandler }). After the 'todos' database is opened, our callback function changeHandler will be called whenever data changes in the database. When the Promise is resolved, the database is ready for use and we hide the loading indicator.

Reload the page and sign in again. You'll see "Loading to-dos..." while the connection to the database is getting established, followed by "Empty", indicating there are currently no to-dos in the database.

Showing the to-dos

If the database has items in it, we'll want to render those under our to-do list. Let's implement that in changeHandler.

    
  

Adding to-dos

Now, let's add a form for creating new to-dos.

Then, let's add the code to handle the form submission.

    
    <!-- application code -->
    <script type="text/javascript">
    …

      function addTodoHandler(e) {
        e.preventDefault()

        const todo = document.getElementById('add-todo').value

        userbase.insertItem({ databaseName: 'todos', item: { 'todo': todo }})
          .then(() => document.getElementById('add-todo').value = '')
          .catch((e) => document.getElementById('add-todo-error').innerHTML = e)
      }

      document.getElementById('login-form').addEventListener('submit', handleLogin)
      document.getElementById('signup-form').addEventListener('submit', handleSignUp)
      document.getElementById('add-todo-form').addEventListener('submit', addTodoHandler)

      document.getElementById('todo-view').style.display = 'none'

    </script>
    
  

In addTodoHandler we get the to-do text from the input, and then call userbase.insertItem with the database name and the object we want the persist. This will return a Promise that resolves when the data is successfully persisted to the database.

Reload the page and add some to-dos. Then, reload the page again and the to-dos should automatically appear after you login. These to-dos have been successfully persisted in the end-to-end encrypted Userbase database.

Updating to-dos

Now, let's add a checkbox to allow to-dos to be marked as completed.

    
    // render all the to-do items
    for (let i = 0; i < items.length; i++) {

      // build the todo checkbox
      const todoBox = document.createElement('input')
      todoBox.type = 'checkbox'
      todoBox.id = items[i].itemId
      todoBox.checked = items[i].item.complete ? true : false
      todoBox.onclick = (e) => {
        e.preventDefault()
        userbase.updateItem({ databaseName: 'todos', itemId: items[i].itemId, item: {
          'todo': items[i].item.todo,
          'complete': !items[i].item.complete
        }})
        .catch((e) => document.getElementById('add-todo-error').innerHTML = e)
      }

      // build the todo label
      const todoLabel = document.createElement('label')
      todoLabel.innerHTML = items[i].item.todo

    …

      // append the todo item to the list
      const todoItem = document.createElement('div')
      todoItem.appendChild(todoBox)
      todoItem.appendChild(todoLabel)
      todosList.appendChild(todoItem)
    }
    
  

Reload the page and complete some to-dos. Their state should persist even after you reload the page and login again.

Deleting to-dos

And finally, let's create a button for deleting a to-do.

    
    // render all the to-do items
    for (let i = 0; i < items.length; i++) {

      // build the todo delete button
      const todoDelete = document.createElement('button')
      todoDelete.innerHTML = 'X'
      todoDelete.style.display = 'inline-block'
      todoDelete.onclick = () => {
        userbase.deleteItem({ databaseName: 'todos', itemId: items[i].itemId })
          .catch((e) => document.getElementById('add-todo-error').innerHTML = e)
      }

      // build the todo checkbox
      const todoBox = document.createElement('input')
      todoBox.type = 'checkbox'
    
  

And now let's append the delete button to the to-do element.

    
    // append the todo item to the list
    const todoItem = document.createElement('div')
    todoItem.appendChild(todoDelete)
    todoItem.appendChild(todoBox)
    todoItem.appendChild(todoLabel)
    todosList.appendChild(todoItem)
    
  

Reload the page and delete some to-dos. They should no longer show up even after you reload the page and login again.

Polishing up

Before we wrap up, let's add two final pieces of account functionality: user logout and automatic login for returning users.

Signing out users

First, let's add a logout button along with a container for error messages.

Then, let's add the code to handle the logout.

    
    <!-- application code -->
    <script type="text/javascript">
    
    …

        .catch((e) => document.getElementById('signup-error').innerHTML = e)
      }

      function handleLogout() {
        userbase.signOut()
          .then(() => showAuth())
          .catch((e) => document.getElementById('logout-error').innerText = e)
      }

      function showTodos(username) {
        document.getElementById('auth-view').style.display = 'none'
        document.getElementById('todo-view').style.display = 'block'

    …

      function showAuth() {
        document.getElementById('todo-view').style.display = 'none'
        document.getElementById('auth-view').style.display = 'block'
        document.getElementById('login-username').value = ''
        document.getElementById('login-password').value = ''
        document.getElementById('login-error').innerText = ''
        document.getElementById('signup-username').value = ''
        document.getElementById('signup-password').value = ''
        document.getElementById('signup-error').innerText = ''
      }

      function changeHandler(items) {
        const todosList = document.getElementById('todos')

    …

      document.getElementById('login-form').addEventListener('submit', handleLogin)
      document.getElementById('signup-form').addEventListener('submit', handleSignUp)
      document.getElementById('add-todo-form').addEventListener('submit', addTodoHandler)
      document.getElementById('logout-button').addEventListener('click', handleLogout)

      document.getElementById('todo-view').style.display = 'none'
    </script>
    
  

The handleLogout function calls userbase.signOut which sends a request to end the user's session. A Promise is returned that resolves when the user is signed out, in which case we hide the to-do view and show the authentication view.

Automatically resuming a session

Let's modify our app to automatically sign in a returning user when the page loads. First, we'll add a loading indicator that will show while the app is trying to automatically sign in the user.

    
    </head>
 
    <body>
      <!-- Loading View -->
      <div id="loading-view">Loading...</div>

      <!-- Auth View -->
      <div id="auth-view">
      <h1>Login</h1>
    
  

Then, let's add the following to our userbase.init call.

    
    <!-- application code -->
    <script type="text/javascript">
    
      userbase.init({ appId: 'YOUR_APP_ID' })
        .then((session) => session.user ? showTodos(session.user.username) : showAuth())
        .catch(() => showAuth())
        .finally(() => document.getElementById('loading-view').style.display = 'none')

    …
    
      document.getElementById('login-form').addEventListener('submit', handleLogin)
      document.getElementById('signup-form').addEventListener('submit', handleSignUp)
      document.getElementById('add-todo-form').addEventListener('submit', addTodoHandler)
      document.getElementById('logout-button').addEventListener('click', handleLogout)

      document.getElementById('todo-view').style.display = 'none'
      document.getElementById('auth-view').style.display = 'none'

    </script>
    
  

We are now hiding the authentication view by default so that it will only show if an existing session can't be resumed.

The userbase.init function returns a Promise that resolves when the SDK has determined if it can reuse the previous session. If so, the user gets automatically logged in, and the session.user object gets set. If there was no previous session, or the session cannot be resumed, the session.user object will not be set. If userbase.init fails, we'll just send the user to the sign in page regardless of the reason.

Next, we want to tell the SDK to persist the session even after we refresh the page or close the browser's window. To do that, we need to set the rememberMe parameter to either 'local' (persists after the window gets closed) or 'session' (persists only when the page gets refreshed) when calling userbase.signIn and userbase.signUp. Let's set ours to 'local'.

    
    <!-- application code -->
    <script type="text/javascript">
      userbase.init({ appId: 'YOUR_APP_ID' })

      function handleLogin(e) {
        e.preventDefault()

        const username = document.getElementById('login-username').value
        const password = document.getElementById('login-password').value

        userbase.signIn({ username, password, rememberMe: 'local' })
          .then((user) => showTodos(user.username))
          .catch((e) => document.getElementById('login-error').innerHTML = e)
      }

      function handleSignUp(e) {
        e.preventDefault()

        const username = document.getElementById('signup-username').value
        const password = document.getElementById('signup-password').value
  
        userbase.signUp({ username, password, rememberMe: 'local' })
         .then((user) => showTodos(user.username))
          .catch((e) => document.getElementById('signup-error').innerHTML = e)
      }
    …

    </script>

    
  

Now, if we sign in and close the page, we will get signed in automatically next time we open it, without having to re-enter our username and password.

What's next?

And that was it! A fully working (but ugly) web app in just 182 lines of code, including markup and comments. If you have any questions, or there's anything we can do to help you with your web app, please get in touch. Thank you!