Userbase

Docs


Getting started

Create a developer account

First, you need a Userbase developer account. You will be able to create a free account when Userbase gets launched. No credit card required.

Install the SDK

Then, you need to include the Userbase SDK into your web app.

You can either include the SDK with a <script> tag:

    
  

Or else, you can include the SDK into your build pipeline:

    
    npm --install --save userbase-js
    
  

Set the Application ID

From your developer account, create a new App and get the Application ID. Then, you just need to configure the Userbase SDK to use it:

    
  

And you're all set.


SDK

Userbase is accessible directly from the browser through a very simple JavaScript SDK. The following is the complete set of APIs that let you create user accounts, handle logins, and persist user data.

Configure

Use this API to configure your Userbase client.

Users

Use these APIs to create user accounts, handle logins and logouts, and resume sessions when a user returns to your web app.

Data

Use these APIs to store and retrieve user data. All data handled by these APIs is highly-durable, immediately consistent, and end-to-end encrypted.


Tutorial

In this tutorial we will build a very basic to-do web app. Even if what you're building has nothing to do with to-dos, the techniques we'll cover here can be applied to many other kinds of web apps.

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, and 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 185 lines. You can also see a live demo of the final result.

Prerequisites

The only requirement is basic familiarity of HTML and JavaScript.

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 a developer account

To complete this tutorial, you'll need to create a Userbase developer account. Upon creation, a default application named "Preview" will be created. Take note of the App ID because you'll need it in a second.

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.

Configuring 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 developer account:

    
    <body>
      <!-- application code -->
      <script type="text/javascript">
        userbase.configure({ 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.configure({ 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)
            .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.

The first thing we do in handleSignUp is call preventDefault() on the submit event. This will prevent the page from submitting to the server. We want to handle this form completely on the client-side.

Next, we get the values of the username and password inputs and call userbase.signUp(username, password). This will request a new account to be created with the Userbase service, and returns a Promise that resolves with a user object.

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 go to your Userbase developer 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 in a bit to change this function to 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.configure({ 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)
          .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 within 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 prevents the default form behavior, extracts the input values from the DOM, and calls userbase.signIn(username, password). This will attempt to sign in the user with the Userbase service, 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, indicate to the user they are logged in, 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.configure({ appId: 'YOUR_APP_ID' })

    …

    </script>
    
  

Then, let's add a function to display this view and initially make it hidden:

      
      <!-- 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 password = document.getElementById('login-password').value

      userbase.signIn(username, password)
        .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)
        .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

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

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

    
  

Then, we'll change showTodos to open a new database with the Userbase service:

    
    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('todos', handleDatabaseChange)
        .catch((e) => document.getElementById('db-error').innerText = e)
    }

    function handleDatabaseChange(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('todos', handleDatabaseChange). The 'todos' parameter is the name of the database we want to open, and handleDatabaseChange is a callback for receiving changes to data in the database. The Userbase service will attempt to open the user's database by the name of 'todos' (creating one if it doesn't already exist). After the 'todos' database is opened, our callback function handleDatabaseChanges 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 the "Loading to-dos..." as a connection to the database is 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 case in handleDatabaseChange:

    
  

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.insert('todos', { '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 first call preventDefault() to stop the default form behavior, pull the to-do text from the input, and then call userbase.insert 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.update('todos', { 
          'todo': items[i].item.todo, 
          'complete': !items[i].item.complete 
        }, items[i].itemId)
        .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.delete('todos', 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 displaying 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 handleDatabaseChange(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 to the Userbase service. 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

Whenever a user logs in, the Userbase SDK will store information about the session in the browser to allow it to be resumed when the user returns after having navigating away.

Let's modify our app to automatically sign in a returning user when the page loads. First, we'll add a view that indicates we are signing in the user:

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

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

In order to automatically resume a session if one is available, we need to add the following to our application code:

    
    <!-- application code -->
    <script type="text/javascript">
    
    …
    
      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'

      userbase.signInWithSession()
        .then((session) => session.user ? showTodos(session.user.username) : showAuth())
        .catch(() => showAuth())
        .finally(() => document.getElementById('loading-view').style.display = 'none')

    </script>
    
  

We hide the authentication view initially, as we'll now only show it if an existing session can't be resumed.

Then, we make a call to userbase.signInWithSession to attempt to sign in the user using an existing session as soon as our app loads.

The userbase.signInWithSession 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.signInWithSession fails, we'll just send the user to the sign in page regardless of the reason.

What's next?

And that was it! A fully working (but ugly) web app in just 185 lines of code, including markup and comments.

In a real project you'll likely want to use a more sophisticated approach. For instance, you may want to use React to control the DOM, or a module bundler to package your application from multiple files. At the very least, you'll want to add some styling.

We are working on a collection of new tutorials and sample applications that will show you how to do all these things with Userbase, and more. You can subscribe to our mailing list to get updates like these in your inbox.

If you have any questions, or there's anything we can do to help you with your web app, please get in touch via email or Twitter. Thank you!


FAQ

What data gets end-to-end encrypted?

All the data stored using the Insert, Update, Delete, or Transaction APIs is end-to-end encrypted, using keys that never leave the users' devices. Both the stored item and the item ID are end-to-end encrypted.

Other user data and metadata, such as usernames, the timestamp when users signed up, and logs about user activity, are encrypted on the wire and at rest, but are not end-to-end encrypted.

How does Userbase help with GDPR compliance?

Userbase will help you implement the necessary GDPR controls, avoid personal data misuse, and give your users control over their data. More specific information about GDPR will be available soon.

What happens when users login from a new device or browser?

When a user creates a new account, a key gets automatically generated in the user's browser. The user will need this key to encrypt and decrypt data, as well as to login to the account. When the key gets created, it gets saved in the browser's local storage.

When a user tries to login from a new device or browser, the user can automatically have the new device receive the key by logging in from the original device and accepting the key transfer request. If that's not possible, the user can also input the key directly while logging in.

What happens if a user loses the key?

Users should always keep a backup copy of their key in a safe place, such as a password manager. If a user loses access to all devices that were used to access the account, the backup key will be necessary to recover the account. The key is required to log into the user account and to decrypt user data.

How can users find their key?

The key is a simple string that is available from the Userbase SDK. Your web app should inform users to make a backup of their key when they sign up, and should provide a way to display the key when a user is logged in.

What happens when users forget their password?

The key and the password are unrelated, so unlike keys, passwords can be reset if forgotten. If you let users specify an email address when they sign up, users will be able to reset their passwords on their own, through a typical password reset workflow via email. If you choose to not collect emails, then you can reset a user's password manually from the Admin panel.

What can I see about my users?

You will be able to see the list of usernames, the time the accounts were created, and any other information you collect during signup, such as users' email, full name, address, etc.

What can I do with my users?

From the Admin panel, you will be able to see all your users, reset user passwords, suspend user accounts, and permanently close user accounts.

How can I require users to pay to access my web app?

Userbase will have an API (coming soon) to let you update user attributes from a server-side endpoint or a cloud function. This will allow you to update a user's payment status in Userbase. Then, your web app can retrieve this information and control its functionality according to the user's payment status.

How durable is the data stored in Userbase?

Userbase only acknowledges data modification requests after the data has been successfully persisted to Amazon DynamoDB. This is a highly-durable service that synchronously replicates data to at least 3 hosts in isolated geographical zones before acknowledging a write operation. Userbase has continuous backups enabled on all its DynamoDB tables with a 35 day recovery window.

What services does Userbase depend on?

Userbase runs entirely in Amazon Web Services, in the us-east-1 region. The availability of the Userbase service depends on Amazon EC2, Amazon S3, and Amazon DynamoDB.