Skip to content

A simpler approach to storing transient data


At some point, your app will need to store some data. It's unavoidable and it can be a chore. A chore because not every case requires a full blown backend. Every so often, we just need a quick way to persist some data without comitting to a dedicated database at the back - or maybe we just don't want to pollute an existing storage with temporary data that is ephermal in nature and only needed for a short little while.

Demo apps, mock ups, static pages... are all use cases for this service. Well, say hello to PrefsDB!

  • Free, serverless, cloud storage
  • No accounts, no logins required.
  • No databases to create and maintain.
  • No backend: allow your apps to save and retrieve data in NoSQL data storage.
  • Secured with end-to-end encryption, RLS, and isolated API tokens.
  • Data life-span managed with TTL.

The idea is to be able to quickly store and retrieve some data with minimum fuss. With that goal in mind, the API was designed to be as frictionless as possible. The service does not require a login or accounts on your part (however advisable, but more on that later).

User-Pays Model

PrefsDB is free for developers. Every user of your app gets their own API key, and every key comes with a free allowance of requests and storage. Your costs don't grow with your user count: whether your app has 1 user or 1 million users, it costs you nothing to run. PrefsDB gives you scalable infrastructure, low barrier to entry, and fast deployments.

Each instance of your app will generate an isolated API Key that is unique to your app's user. That token will govern resource usage for that user. The free allowance on each key is plentiful for a typical user of an average web app.

When one of your users needs more than the free allowance, they can upgrade their own key from their private dashboard: the sign-in link they receive with their key. They pay for their own upgrade; you never receive a bill for your users' usage.

Coming soon

Upgrades aren't available yet. Until then, every key stays on the free allowance.

With PrefsDB you can build and distribute applications without worrying about server bills, scaling costs, or surprise usage spikes (and associated spike charges).

Advantages of the User-Pays Model

  1. No server costs. You will never wake up to a surprise cloud bill due to a traffic spike over a weekend.
  2. No accounts, no logins. You can use PrefsDB like a glorified LocalStorage or you can implement a classic login with ease.
  3. Built-in Security. Isolated API Tokens for each instance of your app limits bad actors to their own environment.
  4. Built-In Anti-Abuse. Every key has its own limits, so one heavy or abusive user can't use up anyone else's allowance, or run up a bill for you.
  5. Free to start, room to grow. Users who need more upgrade their own key; nobody else's plan changes.

A better alternative to LocalStorage?


So you're building something and want to let your users save some data without involving a database or a dedicated backend. There are options: your own VPS, cloud solutions, etc... but you're thinking it's all an overkill for this and are seriously considering localStorage.

LocalStorage (an implementation of Web Storage API standard) in JavaScript allows web applications to store data locally within the user's browser – with no expiration date. The data isn’t deleted when the browser is closed, and is available when the browser is opened again.

So far so good. It operates on a simple key-value basis and the data remains persisted even after the user closes the browser or navigates away from the page. The API methods are simple to understand and provide a convenient way to maintain state and store user preferences without relying on server-side storage.

LocalStorage, while often a valid choice for many use cases, it suffers from two fundamental flaws:

  1. Data is lost when user resets the browser (or the device).
  2. Data does not persist across devices. Settings saved on one device cannot be accessed on another.

How does PrefsDB work?


This is where prefsdb shines. It can be used like a typical serverless cloud storage with auth, or it can be utilized in a way similar to localStorage where no auth from the end user is needed (remmeber: each API token is unqiue to the user/instance of the app).

PrefsDB uses on the fly generated API keys (tokens) - on as needed basis - using the credentials/secrets provided by your users: a login handle , a passphrase, "password", anything at all that would make the token unique for your user.

Those credentials are hashed in the browser: the credentials themselves are never transmitted and never stored anywhere. (If you pass the user's email address to verify a new key, that address is sent.)

Once that step is completed you can store and retrieve data for that particular user.

More practical example: let's say you you built a frontend and now need to preserve some user-specific data: user's preferences, settings, app's state, progress, etc. etc. So you add a credentials collecting form on your page.

The code might look similar to this:

    <input type="text" id="email">
    <input type="password" id="password">

Notice

Notice how we're not using <form> tag. That's because we will not be submitting a form. We're also not asking the user to "create an account" first. We simply create an API token using the supplied credentials and store data.

What's next? Well, under normal circumstances, now wyou'd need to spend time building and provisioning a backend to store that data, right? You may already have something in place, and that's great. But if you don't, PrefsDB can come in handy.

A simple call to PrefsDB API can save data for your user.

/* retrieve user input */
const email = document.getElementById("email").value;
const pass  = document.getElementById("password").value;

/* store data once key is generated */
const db = await PrefsDB.getkey("project_name", [email, pass, salt], { email });
await db.key("data").write(data);
curl -s -c cookies.txt -G https://prefsdb.com/api/v1/getkey/ \
  --data-urlencode "name=project_name" --data-urlencode "seed=$SEED" \
  --data-urlencode "namespace=$PREFSDB_NAMESPACE" --data-urlencode "email=$EMAIL" -d offline=1
# KEY = the "token" from the response
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/write/ --data-urlencode "data=$DATA"

Tip

A successful call to PrefsDB.getkey() or PrefsDB.createkey() gives you a PrefsDB instance set up with the appropriate API security tokens. The first time a key is created, the user gets an email with a verification link to click before the key can store data. The email names your site, and can show your app's name and logo (Your site's name and logo).

And that's it. This is the most basic use case for PrefsDB. The password is never stored anywhere and never leaves the client: PrefsDB hashes the seed values in the browser. (The email address is sent only so a new key can be verified.) That data can be retrieved with corresponding .read() call.

    const db = await PrefsDB.getkey("project_name", [email, pass, salt]);
    const result = await db.key("data").read();
    data = result.value;

Same as above but using .then():

    PrefsDB.getkey("project_name", [email, pass, salt])
        .then((db) => db.key("data").read())
        .then((result) => { data = result.value; });

Why PrefsDB is a better localStorage alternative?


Yes, local storage in the web browser is very convenient and easily accessible. However, it suffers from a few major shortcomings:

  1. Local storage does not survive data wipe or device reset. When users clear the browser cache they will loose the data.
  2. Local storage is not secure: data stored in plain text. Susceptible to injection attacks.
  3. Rather low limit of 5 MB of storage.
  4. No device switching. Data saved with LocalStorage on one device cannot be retrieved on another.
  5. LocalStorage offers synchronous operation only. Each read or write operation will be performed seqientially - one-at-a-time. Prefsdb.com API was designed from the ground up to be asynchronous.
  6. LocalStorage can't be used by web workers. If you build an application that does background processing, localstorage may be out of reach.
  7. Incognito mode issues. Incognito or "private" browsing tabs will not retain local or session storage.

PrefsDB API addresses all of the above shortcomings with a simple, clean, and secure API.

With PrefsDB:

  1. Your app's data will survive a device reset.
  2. Data can be restored on any other device.
  3. No 5 MB total storage limits.
  4. User's data is protected with isolated API keys, built-in RLS, and end-to-end encryption. You can always store self-encrypted data.
  5. Works in private/incognito mode across browsers and tabs.

Data security


The API utilizes Password-Based Encryption process to generate a unique API token which will govern the use of your or your users' data. Any credentials supplied by your users or secrets supplied by your app are contained within the scope of the client. They never leave the browser.

Data-in-motion.

All communication and traffic between the client (your app) and the server server is secured with industry standard HTTPS protocol. Even though your app may not use http (not recommended), and and all communication with the API is over HTTPS.

Data-at-rest.

All data is stored encrypted secured by AES-256 encryption algorythm.

API token isolation

Token-gated architecture seperates token space and any and all API keys (tokens) are bounded to their origin.

Row-Level-Security.

Your individual customer's data is gated by the boundaries set by the unique API token isolated to each instance of your app.

Data collection types


As of now there are two types of data that can be managed with prefsdb.com:

  1. Key/value pairs and
  2. Lists.

Key-Value pairs

Key/value pairs are fairly self-explanatory, each "key" may be assigned a "value". Duplicate assignments will result in a value (for a given key) be overwritten.

await db.key("user").write({ score: 200, level: 3 });
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/write/ --data-urlencode 'user={"score":200,"level":3}'

Lists and collections

Lists are unordered collections of values. We add and remove items to and from a list by specifying a list's name and a value we want added (or removed).

await db.list("signups").add("jane.doe@somewhere.com");
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/write/ -d list=signups --data-urlencode "*=jane.doe@somewhere.com"

Retrieve all items in a list

// uses a callback
db.list("signups")
    .read({
        onsuccess: (data) => {
            console.log(data.values ?? data.value);  // one entry comes back as .value
        }
    });
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/read/ -d list=signups -d "*="

Organizing and grouping data

Each API token can organize and store your data under any number of "projects", "domains", or "subdomains". There is no inherent meaning behind those terms, they are just grouping keywords for organizing your data. For instance "domain" may refer to an internet domain or a knowledge base domain.

const gameJson = { level: 5, score: 200 };

await db.domain("ultimafaux")
    .key("user")
    .write(gameJson);
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/write/ \
  -d domain=ultimafaux --data-urlencode 'user={"level":5,"score":200}'

Callbacks and Promises

Retrieve data using callback functions or handle Promise directly, whatever fits your style.

// using .read method with a callback
db.domain("ultimafaux")
    .key("user")
    .read({
        onsuccess: (data) => {
            gameJson = data.value;
        }
    });

Same as above but with a Promise

// using .read method with a Promise
db.domain("ultimafaux").key("user").read()
    .then((data) => {
        gameJson = data.value;
    })
    .catch((error) => {
        console.log(error.error);
    });

Time-To-Live


You can set a time-to-live on any piece of data, in days: it's deleted automatically once that many days have passed since it was written with the TTL (reading it doesn't reset the countdown). Data without a TTL is kept until you delete it. To set a TTL on everything already stored in a scope, use .expire(days), e.g. db.project("myclient").expire(30).

const stuff = { proposal: "..." };

// store it for 10 days
await db.domain("demostuff")
    .project("myclient")
    .ttl(10)
    .key("proposal123")
    .write(stuff);
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/write/ \
  -d domain=demostuff -d project=myclient -d ttl=10 --data-urlencode 'proposal123={"proposal":"..."}'

The above entry will be automatically deleted after 10 days.