Las 3 formas de guardar datos en el navegador con JavaScript

Escrito por picodotdev el , actualizado el .
javascript planeta-codigo
Enlace permanente Comentarios

En el navegador del usuario hay varias formas de guardar información con diferentes propósitos que perdure entre las visitas realizadas en varias sesiones. Los navegadores ofrecen tres formas de guardar datos: cookies, LocalStorage y SessionStorage e IndexedDB cada una con diferentes características y utilizables con código JavaScript.

Las aplicaciones web utilizan la arquitectura cliente/servidor comunicándose mediante la red para enviar y recibir datos. El cliente ya sea un navegador web en un ordenador de escritorio o portátil o bien sea un dispositivo móvil inicia las solicitudes al servidor y el servidor devuelve una respuesta. La respuesta del servidor puede ser contenido HTML o datos en formato JSON en el caso de recursos REST o GraphQL.

Aún con la cada vez mayor ancho de banda de las redes cada petición al servidor con comunicación mediante la red implica una latencia en la respuesta de unas decenas de milisegundos que se aprecia en la fluidez de las aplicaciones. Para evitar estas latencias y mejorar la experiencia de uso de las aplicaciones web hay varias estrategias, reducir el número de peticiones de una página web o aplicación web, reducir la cantidad de datos transmitidos y cuando sea posible cachear los recursos y datos para no tener que solicitarlos en cada petición ni en futuras visitas al servidor.

Los navegadores web modernos ofrecen tres formas diferentes de almacenar o persistir datos en lado del cliente cada una siendo más apropiada según las necesidades, estas tres formas de guardar datos son con las cookies, LocalStorage o SessionStorage y finalmente IndexedDB. Los datos están accesibles aún sin conexión con el servidor desde donde se descargó la página siguiendo la política de mismo origen para mantener la seguridad y que solo la página origen tenga acceso a los datos almacenados. Una aplicación puede utilizar una o varias de estas formas de guardar datos al mismo tiempo que perduran al cierre del navegador y de las páginas estando disponible en futuras sesiones.

En los navegadores se pueden inspeccionar estos datos almacenados en el lado del cliente utilizando las herramientas para desarrolladores, en el navegador Firefox en la sección Almacenamiento de las herramientas para desarrolladores.

Dado que los datos se almacenan en el navegador del usuario hay que tener en cuenta en que el propio usuario tiene acceso a ellos y es capaz de modificarlos, de modo que si estos datos se envían al servidor hay que tratarlos como una fuente de datos no confiable y validarlos en el lado del servidor si es necesario para evitar fallos de seguridad ni ser datos que comprometan la seguridad.

Guardar datos en el navegador con cookies

El protocolo HTTP es un protocolo sin estado, esto significa que cada petición al servidor es independiente y no comparten información. Las cookies son la forma de convertir el protocolo HTTP a un protocolo con estado identificando al usuario en las diferentes peticiones. Las cookies son unos pequeños datos guardados por el navegador y enviados al servidor en cada petición al servidor.

Las cookies se utilizan para mantener la sesión, para ofrecer personalización o para realizar seguimiento de usuarios. Cuando se realiza la autenticación en un servidor la sesión se mantiene creado una cookie que contiene un identificativo de sesión y en el lado del servidor el identificativo de la sesión se mantiene en memoria o externalizada del servidor en una base de datos como Redis o relacional. El identificativo de la sesión guardado en una cookie no es más que al menos 128 bits aleatorios únicos para cada usuario.

Las propiedades de las cookies son:

  • Se mantienen en el cliente.
  • Se envían en cada petición al servidor.
  • Su tamaño es muy reducido, no pueden superar los 4 KiB.
  • Tienen una fecha de expiración.
  • Tienen un nombre y guardan un valor.
  • Las cookies solo se envían al dominio origen.
  • Pueden crearse sin la posibilidad de que desde JavaScript sean accesibles por seguridad.
  • Se pueden crear tanto en el lado del servidor, como en el lado del cliente con JavaScript.

Desde JavaScript se pueden crear cookies, buscar por nombre, obtener sus valores, modificar y eliminar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Crear y modificar una cookie de nombre name
document.cookie = "name=value";

// Comprobar si existe una cookie de nombre name
const exists = document.cookie.split(';').some(function(item) {
   return item.trim().indexOf('name=') == 0;
});

// Obtener el valor de la cookie de nombre name
const value = document.cookie.split('; ').find(row => row.startsWith('name=')).split('=')[1];

// Eliminar la cookie cookie de nombre name
document.cookie = "name=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
Cookies.js

Inspección de datos almacenados con cookies

Guardar datos en el navegador con LocalStorage y SessionStorage

Las cookies tiene la limitación de que son pequeñas y de enviarse en cada petición al servidor incluidas las peticiones de solicitudes de recursos como imágenes y hojas de estilo lo que aumenta la cantidad de datos transmitidos en el caso de realizar por cada página solicitada con muchos recursos un gran número de peticiones. Aunque el navegador soporte la Web Storage API algunos navegadores en el modo privado y restringidos impiden su uso para proteger la privacidad y el rastreo de los usuarios.

El sistema de persistencia LocalStorage y SessionStorage tiene las propiedades:

  • La cantidad de datos que se pueden guardar es de hasta 5 MiB.
  • Los datos almacenados no se transmiten, solo se almacenan en el navegador.
  • No tienen fecha de expiración.
  • Almacenan datos clave-valor.
  • Las claves-valor están asociadas a un dominio.

La diferencia entre LocalStorage y SessionStorage está en que en el último caso los datos son eliminados al cerrar todas las pestañas del navegador del dominio asociado al SessionSotrage, sus datos están limitados a la sesión. Se permiten las operaciones de inserción, lectura, modificación, eliminación y búsqueda.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Crear una clave-valor
localStorage.setItem('key', 'value');

// Ontener el valor de una clave
let value = localStorage.getItem('key');

// Eliminar una clave
localStorage.removeItem('key');

// Eliminar todas las claves
localStorage.clear();

// Guardar un valor en formato JSON
value = JSON.stringify({"key1": true, "key2": 42, "key3": "Hello World!"});
localStorage.setItem('key', value);

// Obtener el valor de una cadena guardada en formato JSON
const string = localStorage.getItem('key');
value = JSON.parse(string);

// Buscar elementos
let keysArray = [];
for (let i = 0; i < localStorage.length; ++i) {
    keysArray.push(localStorage.key(i));
}
console.log(keysArray);
LocalStorageSessionStorage.js

Otra de las características de LocalStorage y SessionStorage es que permiten establecer un manejador de evento o listener cuando se realizan cambios. La singularidad de este mecanismo es que permite comunicar varias pestañas de la misma aplicación sin necesidad de realizar la comunicación a través del servidor.

1
2
3
4
5
6
7
window.addEventListener('storage', function(e) {  
    console.log("Key: " + e.key);
    console.log("Old value: " + e.oldValue);
    console.log("New value: " + e.newValue);
    console.log("Url: " + e.url);
    console.log("Storage area: " + JSON.stringify(e.storageArea));
});
StorageListener.js

Inspección de datos almacenados con LocalStorage

Guardar datos en el navegador con IndexedDB

En el caso de querer grandes cantidades de datos o de poder buscar datos por varias claves la otra forma disponible es IndexedDB. Sus propiedades son:

  • Permite almacenar grandes cantidades de datos.
  • Permite almacenar datos estructurados.
  • Cada base de datos tiene múltiples espacios de almacenamiento e índices.
  • Permite búsquedas por varias claves e índices eficientemente.
  • Soporta transaccionalidad.

Los espacios de almacenamiento de datos relacionados se denominan objectStore. Los datos se acceden por una clave primaria pero con la diferencia de que puede haber varios índices, cada uno indexando los datos con una clave diferente. Se permiten las operaciones de inserción, lectura, modificación, eliminación y búsqueda.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Funciones de utilidad para convertir la API de listeners de IndexedDB a promesas
function toRequestPromise(request) {
    return new Promise((resolve, reject) => {
        const unlisten = () => {
            request.removeEventListener('success', success);
            request.removeEventListener('error', error);
        };
        const success = () => {
            resolve(request.result);
            unlisten();
        };
        const error = () => {
            reject(request.error);
            unlisten();
        };
        request.addEventListener('success', success);
        request.addEventListener('error', error);
    });
  }
  
  function toTransactionPromise(database, transaction) {
    return new Promise((resolve, reject) => {
        const unlisten = () => {
            transaction.removeEventListener('success', success);
            transaction.removeEventListener('error', error);
        };
        const success = (e) => {
            resolve(database, e);
            unlisten();
        };
        const error = (e) => {
            reject(database, e);
            unlisten();
        };
        transaction.addEventListener('complete', success);
        transaction.addEventListener('error', error);
    });
  }

// Abrir una base de datos
var request = indexedDB.open('database', 1);

// Crear los stores de la base de datos y los índices
request.onupgradeneeded = function(e) {
    const database = e.target.result;

    // Crear un objectStore y un índice
    const objectStore = database.createObjectStore("store", { keyPath: "id" });
    objectStore.createIndex('dni', 'dni', {unique: false});

    console.log('database created');
};

toRequestPromise(request).then(function(database) {
    // Eliminar todos los datos de un store
    var transaction = database.transaction('store', 'readwrite');
    var store = transaction.objectStore('store');
    store.clear();
    return toTransactionPromise(database, transaction);
}).then(function(database) {
    // Insertar datos
    var transaction = database.transaction('store', 'readwrite');
    var store = transaction.objectStore('store');
    var item = { id: 1, name: 'picodotdev', dni: '00000000A' };
    store.add(item);
    return toTransactionPromise(database, transaction);
}).then(function(database, e) {
    // Obtener datos de un store
    var transaction = database.transaction('store', 'readonly');
    var store = transaction.objectStore('store');
    store.get(1);
    return toTransactionPromise(database, transaction);
}).then(function(database, e) {
    // Obtener datos de un store por índice
    var transaction = database.transaction('store', 'readonly');
    var store = transaction.objectStore('store');
    var index = store.index('dni');
    index.get('00000000A');
    return toTransactionPromise(database, transaction);
}).then(function(database, e) {
    // Modificar datos
    var transaction = database.transaction('store', 'readwrite');
    var store = transaction.objectStore('store');
    var item = { id: 1, name: 'picodotdev', dni: '11111111A' };
    store.put(item);
    return toTransactionPromise(database, transaction);
}).then(function(database, e) {
    // Eliminar datos
    var transaction = database.transaction('store', 'readwrite');
    var store = transaction.objectStore('store');
    store.delete(1);
    return toTransactionPromise(database, transaction);
});
IndexedDB.js

Inspección de datos almacenados con IndexedDB
Comparte el artículo: