Module 10 - Saving Data

Introducing Cookies

Definition and Purpose

Cookies are small pieces of data stored on the user's device by the web browser. They serve the purpose of persistently storing information that can be retrieved and utilized across different sessions or page visits. The data is typically in the form of key-value pairs, and cookies play a crucial role in maintaining stateful information on the client side.

Small pieces of data stored on the user's device

Cookies are text files that contain small amounts of information. They are stored on the user's device, commonly as text strings. This data is managed by the web browser and sent back and forth between the client and server with each HTTP request, enabling the retention of user-specific information.

Used to store information persistently across sessions

One of the primary purposes of cookies is to persistently store information, ensuring that data remains available even when the user navigates away from a webpage and returns later. This is achieved by setting an expiration date for the cookie.

Examples and Best Practices

Example 1: Creating a Simple Cookie

// Set a cookie with the key "username" and value "John Doe"
document.cookie = "username=John Doe";

Example 2: Reading Cookies

// Retrieve all cookies
const allCookies = document.cookie;

// Parse individual cookies into an object
const cookies = Object.fromEntries(
allCookies.split("; ").map(cookie => cookie.split("="))
);

// Access a specific cookie value
const username = cookies.username;

Best Practice 1: Be Mindful of Size Limitations

Cookies have size limitations (commonly a few kilobytes). Avoid storing large amounts of data in cookies to ensure compatibility and optimal performance.

Best Practice 2: Understand Session vs. Persistent Cookies

Session cookies are temporary and expire when the browser is closed, while persistent cookies have a specified expiration date. Choose the appropriate type based on the data's lifecycle.

In summary, cookies are essential for persistently storing small amounts of information on the client side. They enable web applications to remember user preferences, maintain authentication states, and provide a personalized browsing experience. Understanding their definition, purpose, and best practices is fundamental for anyone learning JavaScript and web development.



Creating Cookies

document.cookie property

The document.cookie property is the primary interface for creating and managing cookies in JavaScript. It allows you to set, read, and modify cookies associated with the current document.

Syntax and format

The document.cookie property is a string that contains a semicolon-separated list of key-value pairs representing individual cookies. Each key-value pair is separated by an equals sign (=).

// Set a cookie with the key "username" and value "John Doe"
document.cookie = "username=John Doe";

Adding key-value pairs

You can add multiple key-value pairs to the document.cookie property by separating them with semicolons.

// Set multiple cookies
document.cookie = "username=John Doe; expires=Thu, 01 Jan 2025 00:00:00 UTC; path=/";
document.cookie = "language=English; max-age=3600; secure";

Set expiration date for cookies

Cookies can have an optional expiration date, determining how long they persist on the user's device.

1. Session cookies vs. persistent cookies

Session Cookies: These cookies are temporary and expire when the browser is closed. They don't have an explicit expiration date.

// Set a session cookie
document.cookie = "sessionCookie=value";

Persistent Cookies: These cookies have a specified expiration date, allowing them to persist beyond the current session.

// Set a persistent cookie (expires on January 1, 2025)
document.cookie = "persistentCookie=value; expires=Thu, 01 Jan 2025 00:00:00 UTC";

Examples and Best Practices

Example 1: Creating a Persistent Cookie

// Set a persistent cookie with the key "user_id" and value "123"
const expirationDate = new Date();
expirationDate.setFullYear(expirationDate.getFullYear() + 1); // Expires in one year
document.cookie = "user_id=123; expires=" + expirationDate.toUTCString();

Example 2: Creating a Session Cookie

// Set a session cookie with the key "visited" and value "true"
document.cookie = "visited=true";

Best Practice 1: Use UTC for Expiration Dates

When setting an expiration date for cookies, it's a best practice to use Coordinated Universal Time (UTC) to ensure consistency across different time zones.

Best Practice 2: Be Explicit About Path

Specify the path for the cookie to ensure it's accessible only on specific pages. If not specified, the cookie will be associated with the current page's path by default.

Understanding how to create cookies using document.cookie and setting appropriate expiration dates is crucial for managing user data effectively in web applications. Always consider the lifespan of the information you are storing and follow best practices to enhance the security and efficiency of your cookie management.



Accessing Cookies

Accessing cookies using document.cookie

To read cookies, you can access the document.cookie property, which contains a semicolon-separated string of all the cookies associated with the current document.

Retrieving all cookies

const allCookies = document.cookie;

This code retrieves all cookies as a single string. The string contains key-value pairs separated by semicolons.

Parsing individual cookies

To work with individual cookies, you can parse the string into an object.

const cookies = Object.fromEntries(
allCookies.split("; ").map(cookie => cookie.split("="))
);

Here, the split method is used to separate each key-value pair. The resulting array is then transformed into an object using Object.fromEntries.

Examples and Best Practices

Example 1: Retrieving All Cookies

// Get all cookies
const allCookies = document.cookie;
console.log(allCookies);

Example 2: Parsing Individual Cookies

// Parse individual cookies into an object
const cookies = Object.fromEntries(
document.cookie.split("; ").map(cookie => cookie.split("="))
);
console.log(cookies);

Best Practice 1: Handle Empty Cookies

Before parsing cookies, ensure that there are cookies to parse. If document.cookie is empty, attempting to split it will result in unexpected behavior.

const allCookies = document.cookie.trim();
if (allCookies !== "") {
const cookies = Object.fromEntries(
allCookies.split("; ").map(cookie => cookie.split("="))
);
console.log(cookies);
} else {
console.log("No cookies found.");
}

Best Practice 2: Decoding Cookie Values

Cookie values are URL-encoded. When retrieving values, it's essential to decode them using decodeURIComponent to handle special characters.

const username = cookies.username ? decodeURIComponent(cookies.username) : null;
console.log(username);

Reading cookies is a fundamental part of working with client-side data. By understanding how to access and parse cookies using document.cookie, developers can retrieve and utilize stored information. Be mindful of empty cookies, decode values when necessary, and use this knowledge to build dynamic and personalized web experiences.



Modifying Cookies

Updating cookie values

Once a cookie is set, you can update its value by reassigning a new value to the document.cookie property.

// Update the value of the "username" cookie
document.cookie = "username=New Value";

This will effectively overwrite the existing value of the "username" cookie.

Changing expiration dates

You can modify the expiration date of a cookie to control how long it persists on the user's device.

// Change the expiration date of the "sessionCookie" to 1 hour from now
const expirationDate = new Date();
expirationDate.setTime(expirationDate.getTime() + 60 * 60 * 1000); // 1 hour in milliseconds
document.cookie = "sessionCookie=value; expires=" + expirationDate.toUTCString();

Deleting cookies

To delete a cookie, you can set its expiration date to a time in the past. This instructs the browser to remove the cookie.

// Delete the "username" cookie
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";

Setting expiration to the past

By setting the expiration date to a time in the past (commonly the epoch), the browser interprets the cookie as expired and removes it.

Examples and Best Practices

Example 1: Updating Cookie Value

// Assume we have an existing "counter" cookie
const currentCounter = parseInt(cookies.counter) || 0;

// Increment the counter and update the cookie
document.cookie = "counter=" + (currentCounter + 1);

Example 2: Changing Expiration Date

// Extend the expiration date of the "sessionToken" cookie to 1 day from now
const expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + 1); // 1 day in the future
document.cookie = "sessionToken=value; expires=" + expirationDate.toUTCString();

Example 3: Deleting a Cookie

// Delete the "language" cookie
document.cookie = "language=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";

Best Practice 1: Be Explicit About Path and Domain

When modifying or deleting cookies, be explicit about the path and domain to ensure consistency and avoid unexpected behavior.

document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=example.com;";

Best Practice 2: Update Cookies Securely

When modifying or updating cookies containing sensitive information, use secure connections (HTTPS) to protect data during transmission.

document.cookie = "secureCookie=value; secure;"; // This cookie will only be sent over HTTPS connections

Understanding how to update, modify, and delete cookies is essential for maintaining accurate and secure client-side data. Practicing caution, being explicit about paths and domains, and utilizing secure practices ensure the effective management of cookies in a web application.



Intro to Local Storage

When working with JavaScript, we often need a way to save data directly in the browser so that it can be used later without needing to reload it from a server. Browser storage allows us to do just that, providing a place to store small amounts of data locally on a user’s device.

Why Use Browser Storage?

Imagine you’re creating a website with a color theme that users can personalize. If users select a color, we can store that preference using browser storage so it’s saved even if they leave the page. This is useful for data that should persist across visits, like user settings or saved items in a shopping cart.

Types of Browser Storage:

  1. Local Storage: This is one type of browser storage that allows data to be stored and retrieved even after the browser is closed and reopened. Local storage is great for persistent data that you want to be available long-term on a specific device.
  2. Session Storage: Unlike local storage, session storage is only accessible while the browser tab is open. When the tab is closed, the data in session storage is cleared. It’s a good choice for temporary data that only needs to last as long as a user is on the page.

In this part, we’ll focus on local storage and learn how to store data using JavaScript. By the end of this section, you’ll know how to save, retrieve, and clear data from local storage, as well as why and when it’s useful to do so.



Local Storage API

The local storage API in JavaScript gives us a simple and effective way to store data on a user’s browser that persists across sessions. This means users can close the browser, come back, and still access their saved data.

Local storage uses a key-value pair system where each piece of data is associated with a unique key. Data is stored as strings, so when saving complex data structures like objects or arrays, we’ll need to convert them into a string format. Let’s walk through the core methods and explore how each one works.

localStorage.setItem(key, value)

This method saves data to local storage. It requires two parameters:

  • key: A unique string identifier for the data.
  • value: The data we want to store, which must be a string.

Example:

localStorage.setItem('username', 'Alice');


In this example, we are saving the string "Alice" with the key "username". Now, as long as the user doesn’t clear their local storage, this value will remain available even after they close and reopen the browser.

Considerations:

Since local storage only accepts string values, if you want to store a number, object, or array, you must convert it to a string using JSON.stringify(). We’ll cover this in a later section.

localStorage.getItem(key)

The getItem method retrieves data from local storage. It takes a single parameter:

key: The unique identifier for the data you want to retrieve.

If the specified key exists, this method returns the stored value as a string. If the key does not exist, it returns null.

Example:

let username = localStorage.getItem('username');
console.log(username); // Output: “Alice”


Here, we’re retrieving the value associated with "username". If this key doesn’t exist, getItem would return null, which is a useful way to check if a key exists in local storage.

localStorage.removeItem(key)

The removeItem method deletes a specific item from local storage. It requires:

key: The unique identifier of the item to be removed.

Example:

localStorage.removeItem('username');

 

After executing this code, the "username" key and its associated data will be removed from local storage. Attempting to retrieve it afterward with getItem will return null.

localStorage.clear()

This method removes all data from local storage. Unlike removeItem, it doesn’t require any parameters and will delete everything saved in local storage.

Example:

localStorage.clear();


Use clear() with caution, as it erases all data, not just specific items. This can be useful if you want to reset all saved settings or preferences in an application.

Important Considerations and Limitations

  1. Data Limitations: Local storage has a storage limit, typically around 5-10 MB per domain, depending on the browser. While this is usually sufficient for small data, it’s not suitable for large files or significant amounts of data.
  2. String Storage Only: Since local storage only stores strings, any data type that isn’t a string (like objects or arrays) must be converted using JSON.stringify() before saving. To retrieve and use this data, we need to parse it back into its original format with JSON.parse().
  3. Security Concerns: Local storage is accessible via JavaScript on the client side, making it vulnerable to Cross-Site Scripting (XSS) attacks. Avoid storing sensitive information (like passwords or user tokens) in local storage. For secure data storage, use other solutions like server-side storage.


Saving Simple Data

Now that we’ve covered the basics of local storage, let’s practice saving and retrieving simple data types. Local storage is often used to store data that a user has entered, such as a name or a favorite color, and retrieve it later. In this section, we’ll explore saving basic data types like strings and numbers.

Saving a String with localStorage.setItem

Storing a string is straightforward. Since local storage only accepts data in string format, we can directly save text data without any additional steps.

Example:

localStorage.setItem('greeting', 'Hello, welcome back!');


In this example, we store the string "Hello, welcome back!" with the key "greeting". This means that whenever the user returns to the page, we can retrieve this greeting message from local storage and display it.

Retrieving a String with localStorage.getItem

To retrieve a saved string, we use the getItem method with the key we used to save it. If the key exists, getItem returns the saved value as a string.

Example:

let greeting = localStorage.getItem('greeting');
console.log(greeting); // Output: "Hello, welcome back!"

 

If the "greeting" key does not exist, getItem will return null. This is helpful for checking if data has been saved before trying to use it.

Saving a Number

Although local storage requires values to be strings, we can still save numbers by converting them to strings during storage.

Example:

let visitCount = 5;
localStorage.setItem('visitCount', visitCount.toString());


Here, we’re saving a number (5) as a string by calling .toString() on it. If we need to store a number, converting it to a string is a simple way to ensure local storage can handle it.

Retrieving a Number

When retrieving a number stored as a string, we can convert it back to a number with parseInt() or parseFloat() (depending on whether it’s an integer or decimal).

Example:

let visitCount = parseInt(localStorage.getItem('visitCount'), 10);
console.log(visitCount); // Output: 5

 

Using parseInt with a base of 10 (decimal) converts the string "5" back to the number 5.

Practical Example: Welcome Message with Visit Count

Let’s bring everything together with a practical example that combines a saved message and visit count:

Step 1: Saving a Welcome Message and Visit Count

localStorage.setItem('welcomeMessage', 'Welcome back to our site!');
localStorage.setItem('visitCount', (1).toString());

 

Step 2: Retrieving and Updating the Data

let welcomeMessage = localStorage.getItem('welcomeMessage');
let visitCount = parseInt(localStorage.getItem('visitCount'), 10);
// Display the message
console.log(welcomeMessage); // Output: "Welcome back to our site!"
// Update the visit count and save it again
visitCount += 1;
localStorage.setItem('visitCount', visitCount.toString());
console.log(`You have visited ${visitCount} times.`);

In this example:

  • The welcomeMessage is retrieved and displayed to the user.
  • The visitCount is incremented by one and saved back to local storage, allowing it to persist across sessions.

Considerations for Simple Data

  1. Data Type Conversion: Remember that local storage only accepts strings, so any non-string data needs to be converted.
  2. Error Handling: It’s good practice to check for null when retrieving data to handle cases where the key might not exist.

With these techniques, you can use local storage to save and retrieve simple data types in JavaScript, giving users a more personalized experience that persists across visits. In the next section, we’ll build on these basics to save and retrieve more complex data types like arrays and objects.



Saving Complex Data

Local storage is versatile, but it stores data only as strings. When we need to store more complex data types, like arrays or objects, we can use JSON (JavaScript Object Notation) to convert these structures into strings. This way, we can save structured data that can be retrieved and restored to its original format.

Storing an Array or Object with JSON.stringify()

To store arrays and objects in local storage, we first need to convert them into a JSON string using JSON.stringify(). This method transforms complex data into a format that local storage can handle.

Example of Saving an Array:

let favoriteColors = ['blue', 'green', 'purple'];
localStorage.setItem('favoriteColors', JSON.stringify(favoriteColors));


In this example, we use JSON.stringify(favoriteColors) to convert the array into a string format before saving it with the key "favoriteColors". This makes it easy to retrieve and work with later. 

Example of Saving an Object:

let userSettings = {
theme: 'dark',
fontSize: '16px',
notifications: true
};
localStorage.setItem('userSettings', JSON.stringify(userSettings));


Here, the object userSettings is converted to a JSON string, making it ready to be saved to local storage. 

Retrieving and Parsing Data with JSON.parse()

When we retrieve a JSON string from local storage, we need to convert it back to its original format (array or object) using JSON.parse(). This method takes the stored JSON string and parses it back into a JavaScript array or object.

Example of Retrieving an Array:

let favoriteColors = JSON.parse(localStorage.getItem('favoriteColors'));
console.log(favoriteColors); // Output: ['blue', 'green', 'purple']


If the "favoriteColors" key exists, it will be parsed and converted back into an array. If it doesn’t exist, getItem will return null, which we can handle with a conditional check. 

Example of Retrieving an Object:

let userSettings = JSON.parse(localStorage.getItem('userSettings'));
console.log(userSettings);
// Output: { theme: 'dark', fontSize: '16px', notifications: true }


This code retrieves the JSON string associated with "userSettings" and parses it back into an object. 

Practical Example: Storing and Retrieving User Preferences

Let’s put this all together in a practical example that stores a user’s settings and retrieves them when they return to the page:

Saving User Preferences:

let userPreferences = {
language: 'English',
theme: 'light',
showWelcome: false
};
localStorage.setItem('userPreferences', JSON.stringify(userPreferences));

Retrieving and Applying User Preferences:

let userPreferences = JSON.parse(localStorage.getItem('userPreferences'));
// Check if the preferences exist before trying to use them
if (userPreferences) {
console.log(`Preferred language: ${userPreferences.language}`);
console.log(`Theme: ${userPreferences.theme}`);
console.log(`Show welcome message: ${userPreferences.showWelcome}`);
} else {
console.log('No preferences found. Setting defaults.');
}


This example retrieves the user’s preferences from local storage (if they exist) and displays them. If userPreferences doesn’t exist, we log a message indicating that no data was found. 

Important Considerations When Storing Complex Data

Error Handling

When using JSON.parse(), it’s good to wrap it in a try...catch block. If data in local storage is corrupted or cannot be parsed, JSON.parse() will throw an error.

try {
let data = JSON.parse(localStorage.getItem('someKey'));
} catch (e) {
console.error('Failed to parse data:', e);
}

Null Check 

Always check for null after retrieving data. If a key does not exist in local storage, getItem returns null, so parsing null will cause errors.

String Length Limitations

Remember that local storage has a limit, typically around 5-10 MB, which may restrict the amount and type of data you can store.

Summary

Using JSON.stringify() and JSON.parse(), you can store and retrieve complex data types like arrays and objects in local storage. These techniques allow you to save more sophisticated user data, like preferences, settings, and lists, giving your applications a more personalized and persistent experience. In the next section, we’ll explore creating a project to apply these concepts and deepen your understanding of working with local storage.



Local Storage Security and Limitations

Local storage provides a convenient way to store data on a user’s browser, but it comes with important security considerations and limitations. In this section, we’ll cover best practices and precautions to keep data secure and understand the limitations of local storage.

Security Considerations

Local storage is accessible through JavaScript on the client side, meaning that data stored in it can potentially be accessed by other scripts running on the same page, including malicious ones. Here are some key security points to keep in mind:

Avoid Storing Sensitive Information:

  • Never store sensitive information in local storage, such as passwords, personal identification numbers, or authentication tokens. If accessed by malicious scripts, this information could be exposed to unauthorized users.
  • Instead, sensitive data should be managed by secure, server-side storage solutions that require user authentication and encryption.

Vulnerability to Cross-Site Scripting (XSS) Attacks:

  • Local storage is particularly vulnerable to XSS attacks, which occur when malicious code is injected into a webpage. Since local storage data can be accessed by any JavaScript running on the page, XSS attacks can expose this data.
  • Implementing Content Security Policies (CSPs) and input validation can help prevent XSS vulnerabilities, but it’s a best practice to avoid storing sensitive data in local storage altogether.

Data Access Across Browser Tabs:

  • Local storage is shared across all tabs of the same origin. If users open multiple tabs of your application, they will share the same local storage. This can be useful, but also risky if your data is highly sensitive or if multiple users share the same device.

Limitations of Local Storage

Local storage has a few technical limitations that make it unsuitable for certain use cases. Understanding these limitations can help you design better data storage strategies.

Storage Capacity

  • Most browsers limit local storage to between 5-10 MB per origin (website domain), which is relatively small compared to server-side storage. For large amounts of data, consider server storage or using an indexed database (like IndexedDB), which can handle larger datasets.

String-Only Storage

  • Local storage only supports string data. Any other data type, such as numbers, objects, or arrays, needs to be converted to a string using JSON.stringify().
  • When retrieving this data, you’ll need to parse it back to its original format with JSON.parse(). This conversion can be tedious for complex data or large datasets, and it may contribute to slower performance if data is frequently read and written.

Lack of Expiration

  • Data in local storage does not automatically expire, unlike cookies, which have built-in expiration dates. Once data is saved, it will remain until the user or a script manually deletes it. This can lead to data becoming stale or irrelevant over time.
  • If your data needs expiration control, consider setting a timestamp when saving data and checking it each time you retrieve it. You can then manually clear or refresh data as needed.

No Data Encryption

  • Local storage does not encrypt data, so any data stored in it is stored as plain text. If a device is compromised, the stored data can be accessed without any security barriers.
  • For secure data handling, sensitive information should be encrypted before storage, or managed through secure server-side options rather than local storage.

Summary

Local storage is a helpful tool for saving non-sensitive data that needs to persist across sessions. However, it has several limitations and security concerns, including limited storage capacity, lack of encryption, and vulnerability to XSS attacks. For sensitive data or large-scale storage needs, consider using alternative storage options like secure cookies, server storage, or IndexedDB.

By following these guidelines, you can make informed decisions about when to use local storage and how to use it safely. In the next section, we’ll wrap up with best practices and practical examples to help reinforce these concepts.

Videos for Module 10 - Saving Data

There are no videos yet this term for this Module. Check back soon!