Working with Data in JavaScript

Learn how to work with data in JavaScript by understanding JSON basics, using localStorage and sessionStorage, and storing and retrieving data efficiently. This lesson also walks through building a practical todo list app to apply data handling concepts in real-world scenarios, helping you create dynamic and persistent web applications.

What is Data Storage in Web Development?

Data storage allows your web applications to remember information even after a user closes their browser or refreshes the page. Instead of losing everything when the page reloads, your app can save data locally in the user's browser, creating a seamless and persistent experience.

What is JSON?

JSON (JavaScript Object Notation) is a lightweight format for storing and transporting data. It's easy for humans to read and write, and easy for computers to parse and generate. JSON has become the standard way to exchange data between web applications and servers.

.

JSON Syntax Rules

JSON follows a simple set of rules:

javascript
<ul>
  <li>Data is stored in <strong>key-value pairs</strong>.</li>
  <li>Keys must be <strong>strings wrapped in double quotes</strong>.</li>
  <li>
    Values can be
    <strong>strings, numbers, booleans, arrays, objects, or null</strong>.
  </li>
  <li>Individual data entries are <strong>separated by commas</strong>.</li>
  <li><strong>Curly braces { }</strong> are used to define objects.</li>
  <li><strong>Square brackets [ ]</strong> are used to define arrays.</li>
</ul>

JSON Data Types

String:
javascript
{
  "name": "John Smith",
  "email": "john@example.com"
}
Number:
javascript
{
  "age": 25,
  "price": 19.99,
  "quantity": 100
}
Boolean:
javascript
{
  "isActive": true,
  "hasSubscription": false
}
Array:
javascript
{
  "colors": ["red", "green", "blue"],
  "numbers": [1, 2, 3, 4, 5]
}
Object
javascript
{
  "user": {
    "name": "Jane Doe",
    "age": 30,
    "address": {
      "city": "New York",
      "country": "USA"
    }
  }
}
Null
javascript
{
  "middleName": null,
  "spouse": null
}

Converting Between JavaScript and JSON

JavaScript provides built-in methods to work with JSON:

JSON.stringify() - Convert JavaScript to JSON:
javascript
// JavaScript object
const user = {
  name: "Alice",
  age: 28,
  hobbies: ["reading", "coding", "gaming"]
};

// Convert to JSON string
const jsonString = JSON.stringify(user);
console.log(jsonString);
// Result: '{"name":"Alice","age":28,"hobbies":["reading","coding","gaming"]}'
JSON.parse() - Convert JSON to JavaScript:
javascript
// JSON string
const jsonString = '{"name":"Bob","age":35,"isAdmin":true}';

// Convert to JavaScript object
const user = JSON.parse(jsonString);
console.log(user.name); // "Bob"
console.log(user.age);  // 35
JSON.parse() - Convert JSON to JavaScript:
javascript
// JSON string
const jsonString = '{"name":"Bob","age":35,"isAdmin":true}';

// Convert to JavaScript object
const user = JSON.parse(jsonString);
console.log(user.name); // "Bob"
console.log(user.age);  // 35

Common JSON Operations

Pretty-printing JSON:
javascript
const data = {name: "Charlie", age: 40};
const prettyJson = JSON.stringify(data, null, 2);
console.log(prettyJson);
// Result:
// {
//   "name": "Charlie",
//   "age": 40
// }
Handling nested objects:
javascript
const company = {
  name: "Tech Corp",
  employees: [
    {name: "Alice", role: "Developer"},
    {name: "Bob", role: "Designer"}
  ]
};

const jsonString = JSON.stringify(company);
const parsedData = JSON.parse(jsonString);
console.log(parsedData.employees[0].name); // "Alice"

Understanding localStorage and sessionStorage

Modern browsers provide two powerful APIs for storing data locally: localStorage and sessionStorage. Both allow you to save data as key-value pairs, but they differ in how long the data persists.

localStorage: Persistent Storage

localStorage stores data with no expiration date. The data remains even after the browser is closed and reopened. It's perfect for settings, preferences, or any data you want to keep indefinitely.

Key characteristics:
  • Data persists indefinitely: Stored data remains available until it is manually cleared by the user or application.
  • Domain-specific storage: Data is accessible only within the same domain where it was stored.
  • Storage limit: Browsers typically allow between 5โ€“10MB of storage per domain.
  • String-only storage: All stored values are saved as strings, regardless of their original data type.
  • Synchronous operations: Storage APIs run synchronously, which means they can block the main thread.
Basic localStorage operations:
javascript
// Storing data
localStorage.setItem('username', 'JohnDoe');
localStorage.setItem('theme', 'dark');

// Retrieving data
const username = localStorage.getItem('username');
console.log(username); // "JohnDoe"

// Removing a specific item
localStorage.removeItem('theme');

// Clearing all data
localStorage.clear();

// Checking if a key exists
if (localStorage.getItem('username') !== null) {
  console.log('Username exists!');
}

sessionStorage: Temporary Storage

sessionStorage is similar to localStorage but data only lasts for the duration of the page session. When the browser tab is closed, the data is deleted.

Key characteristics:
  • Session-based data: Data lasts only for the duration of the browser session.
  • Tab-specific storage: Each browser tab maintains its own sessionStorage.
  • Automatic clearing: Stored data is removed when the tab is closed.
  • Storage limit: The storage capacity is typically 5โ€“10MB, similar to localStorage.
  • String-only storage: All values are stored as strings, regardless of their original data types.
Basic sessionStorage operations:
javascript
// Storing data
sessionStorage.setItem('currentPage', '3');
sessionStorage.setItem('scrollPosition', '450');

// Retrieving data
const page = sessionStorage.getItem('currentPage');
console.log(page); // "3"

// Removing data
sessionStorage.removeItem('scrollPosition');

// Clearing all session data
sessionStorage.clear();
Differences Between localStorage and sessionStorage
Feature localStorage sessionStorage
Persistence Permanent Session only
Scope Shared across tabs (same domain) Tab-specific
Expiration Never (until manually cleared) Cleared when tab is closed
Use Cases User preferences, saved data Temporary form data, current state

Storing and Retrieving Complex Data

Since localStorage and sessionStorage only store strings, you need to convert objects and arrays to JSON format.

Storing Objects and Arrays
javascript
// Storing an object
const userProfile = {
  name: "Sarah Johnson",
  email: "sarah@example.com",
  age: 32,
  preferences: {
    theme: "dark",
    notifications: true
  }
};

// Convert to JSON string and store
localStorage.setItem('userProfile', JSON.stringify(userProfile));

// Storing an array
const favoriteColors = ["blue", "green", "purple"];
localStorage.setItem('colors', JSON.stringify(favoriteColors));
Retrieving Objects and Arrays
javascript
// Retrieve and parse object
const storedProfile = localStorage.getItem('userProfile');
const userProfile = JSON.parse(storedProfile);
console.log(userProfile.name); // "Sarah Johnson"

// Retrieve and parse array
const storedColors = localStorage.getItem('colors');
const favoriteColors = JSON.parse(storedColors);
console.log(favoriteColors[0]); // "blue"

Safe Data Retrieval with Error Handling

Always handle potential errors when parsing JSON:

javascript
function getStoredData(key) {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : null;
  } catch (error) {
    console.error('Error parsing stored data:', error);
    return null;
  }
}

// Usage
const userData = getStoredData('userProfile');
if (userData) {
  console.log('User found:', userData.name);
} else {
  console.log('No user data available');
}

Creating Helper Functions

Build reusable functions for common operations:

javascript
// Storage helper object
const storage = {
  // Save data
  save: function(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (error) {
      console.error('Error saving data:', error);
      return false;
    }
  },
  
  // Load data
  load: function(key, defaultValue = null) {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
      console.error('Error loading data:', error);
      return defaultValue;
    }
  },
  
  // Remove data
  remove: function(key) {
    localStorage.removeItem(key);
  },
  
  // Clear all data
  clear: function() {
    localStorage.clear();
  }
};

// Usage
storage.save('settings', {theme: 'dark', fontSize: 16});
const settings = storage.load('settings', {theme: 'light', fontSize: 14});

Building a Complete Todo List Application

Now let's apply everything we've learned by building a fully functional todo list app with data persistence.

The Complete Todo List Code
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Todo List - Never Lose Your Tasks</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      padding: 20px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .container {
      background: white;
      border-radius: 15px;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
      max-width: 600px;
      width: 100%;
      padding: 30px;
    }
    
    h1 {
      color: #333;
      margin-bottom: 10px;
      font-size: 32px;
    }
    
    .stats {
      color: #666;
      font-size: 14px;
      margin-bottom: 25px;
    }
    
    .input-section {
      display: flex;
      gap: 10px;
      margin-bottom: 25px;
    }
    
    #todoInput {
      flex: 1;
      padding: 15px;
      border: 2px solid #e0e0e0;
      border-radius: 8px;
      font-size: 16px;
      transition: border-color 0.3s;
    }
    
    #todoInput:focus {
      outline: none;
      border-color: #667eea;
    }
    
    #addBtn {
      padding: 15px 30px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      border-radius: 8px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      transition: transform 0.2s;
    }
    
    #addBtn:hover {
      transform: translateY(-2px);
    }
    
    #addBtn:active {
      transform: translateY(0);
    }
    
    .filters {
      display: flex;
      gap: 10px;
      margin-bottom: 20px;
    }
    
    .filter-btn {
      padding: 8px 16px;
      border: 2px solid #e0e0e0;
      background: white;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      transition: all 0.3s;
    }
    
    .filter-btn.active {
      background: #667eea;
      color: white;
      border-color: #667eea;
    }
    
    .filter-btn:hover {
      border-color: #667eea;
    }
    
    #todoList {
      list-style: none;
    }
    
    .todo-item {
      background: #f8f9fa;
      padding: 15px;
      margin-bottom: 10px;
      border-radius: 8px;
      display: flex;
      align-items: center;
      gap: 12px;
      transition: all 0.3s;
      animation: slideIn 0.3s ease;
    }
    
    @keyframes slideIn {
      from {
        opacity: 0;
        transform: translateY(-10px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    .todo-item:hover {
      background: #e9ecef;
      transform: translateX(5px);
    }
    
    .todo-item.completed {
      opacity: 0.6;
    }
    
    .todo-item.completed .todo-text {
      text-decoration: line-through;
      color: #999;
    }
    
    .todo-checkbox {
      width: 22px;
      height: 22px;
      cursor: pointer;
    }
    
    .todo-text {
      flex: 1;
      font-size: 16px;
      color: #333;
      word-break: break-word;
    }
    
    .todo-date {
      font-size: 12px;
      color: #999;
      margin-right: 10px;
    }
    
    .delete-btn {
      padding: 8px 15px;
      background: #ff4757;
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      transition: background 0.3s;
    }
    
    .delete-btn:hover {
      background: #ff3838;
    }
    
    .empty-state {
      text-align: center;
      padding: 40px;
      color: #999;
    }
    
    .empty-state-icon {
      font-size: 64px;
      margin-bottom: 15px;
    }
    
    .actions {
      display: flex;
      gap: 10px;
      margin-top: 20px;
    }
    
    .action-btn {
      flex: 1;
      padding: 12px;
      border: 2px solid #e0e0e0;
      background: white;
      border-radius: 8px;
      cursor: pointer;
      font-size: 14px;
      transition: all 0.3s;
    }
    
    .action-btn:hover {
      background: #f8f9fa;
      border-color: #667eea;
    }
    
    .clear-completed {
      background: #ff4757;
      color: white;
      border-color: #ff4757;
    }
    
    .clear-completed:hover {
      background: #ff3838;
      border-color: #ff3838;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>๐Ÿ“ My Todo List</h1>
    <div class="stats">
      <span id="totalTasks">0</span> total tasks ยท 
      <span id="completedTasks">0</span> completed ยท 
      <span id="pendingTasks">0</span> pending
    </div>
    
    <div class="input-section">
      <input 
        type="text" 
        id="todoInput" 
        placeholder="What needs to be done?"
        autocomplete="off"
      >
      <button id="addBtn">Add Task</button>
    </div>
    
    <div class="filters">
      <button class="filter-btn active" data-filter="all">All</button>
      <button class="filter-btn" data-filter="pending">Pending</button>
      <button class="filter-btn" data-filter="completed">Completed</button>
    </div>
    
    <ul id="todoList"></ul>
    
    <div class="actions">
      <button class="action-btn" id="clearAll">Clear All</button>
      <button class="action-btn clear-completed" id="clearCompleted">Clear Completed</button>
    </div>
  </div>

  <script>
    // Todo App Class
    class TodoApp {
      constructor() {
        this.todos = this.loadTodos();
        this.currentFilter = 'all';
        this.init();
      }
      
      // Initialize the app
      init() {
        this.cacheDom();
        this.bindEvents();
        this.render();
      }
      
      // Cache DOM elements
      cacheDom() {
        this.todoInput = document.getElementById('todoInput');
        this.addBtn = document.getElementById('addBtn');
        this.todoList = document.getElementById('todoList');
        this.filterBtns = document.querySelectorAll('.filter-btn');
        this.clearAllBtn = document.getElementById('clearAll');
        this.clearCompletedBtn = document.getElementById('clearCompleted');
        this.totalTasksSpan = document.getElementById('totalTasks');
        this.completedTasksSpan = document.getElementById('completedTasks');
        this.pendingTasksSpan = document.getElementById('pendingTasks');
      }
      
      // Bind event listeners
      bindEvents() {
        this.addBtn.addEventListener('click', () => this.addTodo());
        this.todoInput.addEventListener('keypress', (e) => {
          if (e.key === 'Enter') this.addTodo();
        });
        
        this.filterBtns.forEach(btn => {
          btn.addEventListener('click', (e) => this.setFilter(e.target.dataset.filter));
        });
        
        this.clearAllBtn.addEventListener('click', () => this.clearAll());
        this.clearCompletedBtn.addEventListener('click', () => this.clearCompleted());
      }
      
      // Generate unique ID
      generateId() {
        return Date.now().toString(36) + Math.random().toString(36).substr(2);
      }
      
      // Add new todo
      addTodo() {
        const text = this.todoInput.value.trim();
        
        if (text === '') {
          alert('Please enter a task!');
          return;
        }
        
        const todo = {
          id: this.generateId(),
          text: text,
          completed: false,
          createdAt: new Date().toISOString()
        };
        
        this.todos.push(todo);
        this.saveTodos();
        this.todoInput.value = '';
        this.todoInput.focus();
        this.render();
      }
      
      // Toggle todo completion
      toggleTodo(id) {
        const todo = this.todos.find(t => t.id === id);
        if (todo) {
          todo.completed = !todo.completed;
          this.saveTodos();
          this.render();
        }
      }
      
      // Delete todo
      deleteTodo(id) {
        this.todos = this.todos.filter(t => t.id !== id);
        this.saveTodos();
        this.render();
      }
      
      // Set filter
      setFilter(filter) {
        this.currentFilter = filter;
        this.filterBtns.forEach(btn => {
          btn.classList.toggle('active', btn.dataset.filter === filter);
        });
        this.render();
      }
      
      // Get filtered todos
      getFilteredTodos() {
        switch(this.currentFilter) {
          case 'completed':
            return this.todos.filter(t => t.completed);
          case 'pending':
            return this.todos.filter(t => !t.completed);
          default:
            return this.todos;
        }
      }
      
      // Clear all todos
      clearAll() {
        if (this.todos.length === 0) return;
        
        if (confirm('Are you sure you want to delete all tasks?')) {
          this.todos = [];
          this.saveTodos();
          this.render();
        }
      }
      
      // Clear completed todos
      clearCompleted() {
        const completedCount = this.todos.filter(t => t.completed).length;
        
        if (completedCount === 0) {
          alert('No completed tasks to clear!');
          return;
        }
        
        if (confirm(`Delete ${completedCount} completed task(s)?`)) {
          this.todos = this.todos.filter(t => !t.completed);
          this.saveTodos();
          this.render();
        }
      }
      
      // Format date
      formatDate(dateString) {
        const date = new Date(dateString);
        const now = new Date();
        const diffTime = Math.abs(now - date);
        const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
        
        if (diffDays === 0) return 'Today';
        if (diffDays === 1) return 'Yesterday';
        if (diffDays < 7) return `${diffDays} days ago`;
        
        return date.toLocaleDateString();
      }
      
      // Update statistics
      updateStats() {
        const total = this.todos.length;
        const completed = this.todos.filter(t => t.completed).length;
        const pending = total - completed;
        
        this.totalTasksSpan.textContent = total;
        this.completedTasksSpan.textContent = completed;
        this.pendingTasksSpan.textContent = pending;
      }
      
      // Render todos
      render() {
        const filteredTodos = this.getFilteredTodos();
        
        if (filteredTodos.length === 0) {
          this.todoList.innerHTML = `
            <div class="empty-state">
              <div class="empty-state-icon">๐Ÿ“ญ</div>
              <p>${this.getEmptyMessage()}</p>
            </div>
          `;
        } else {
          this.todoList.innerHTML = filteredTodos.map(todo => `
            <li class="todo-item ${todo.completed ? 'completed' : ''}">
              <input 
                type="checkbox" 
                class="todo-checkbox" 
                ${todo.completed ? 'checked' : ''}
                onchange="app.toggleTodo('${todo.id}')"
              >
              <span class="todo-text">${this.escapeHtml(todo.text)}</span>
              <span class="todo-date">${this.formatDate(todo.createdAt)}</span>
              <button class="delete-btn" onclick="app.deleteTodo('${todo.id}')">
                Delete
              </button>
            </li>
          `).join('');
        }
        
        this.updateStats();
      }
      
      // Get empty state message
      getEmptyMessage() {
        switch(this.currentFilter) {
          case 'completed':
            return 'No completed tasks yet. Keep going!';
          case 'pending':
            return 'No pending tasks. You\'re all caught up! ๐ŸŽ‰';
          default:
            return 'No tasks yet. Add one to get started!';
        }
      }
      
      // Escape HTML to prevent XSS
      escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
      }
      
      // Save todos to localStorage
      saveTodos() {
        try {
          localStorage.setItem('todos', JSON.stringify(this.todos));
        } catch (error) {
          console.error('Error saving todos:', error);
          alert('Failed to save tasks. Storage might be full.');
        }
      }
      
      // Load todos from localStorage
      loadTodos() {
        try {
          const stored = localStorage.getItem('todos');
          return stored ? JSON.parse(stored) : [];
        } catch (error) {
          console.error('Error loading todos:', error);
          return [];
        }
      }
    }
    
    // Initialize the app
    const app = new TodoApp();
  </script>
</body>
</html>

Understanding the Todo List Architecture

Data Structure

Each todo item is stored as an object with the following properties:

javascript
{
  id: "unique_identifier",      // Unique ID for each task
  text: "Buy groceries",         // The task description
  completed: false,              // Completion status
  createdAt: "2024-01-15T10:30:00.000Z"  // Creation timestamp
}

Key Features Explained

1. Data Persistence:
  • Automatic Saving: Todos are saved to localStorage automatically after every change.
  • Persistent Data: Stored todo data remains available even after closing and reopening the browser.
  • Error Handling: Proper error handling is implemented to prevent application crashes if storage operations fail.
2. Filter System:
  • Flexible Views: Users can view all tasks, only pending tasks, or only completed tasks.
  • Visual Feedback: The active filter is clearly highlighted to show the current selection.
  • Non-destructive Filtering: Filters do not modify the underlying data; they only control how tasks are displayed.
3. Statistics Display:
  • Real-time Task Counts: Shows live counts for total tasks, completed tasks, and pending tasks.
  • Automatic Updates: Counts refresh automatically whenever a task is added, completed, or removed.
4. User Experience:
  • Smooth Animations: Tasks animate smoothly when they are added or removed, improving user experience.
  • Keyboard Support: Users can press the Enter key to quickly add new tasks.
  • Confirmation Dialogs: Destructive actions, such as deleting tasks, require user confirmation.
  • Empty State Messages: Clear messages are shown when no tasks are available to guide users.
5. Security:
  • HTML Escaping: User input is properly escaped to prevent XSS (Cross-Site Scripting) attacks.
  • Input Validation: Validation rules ensure that empty or invalid tasks cannot be added.

Advanced Data Storage Techniques

Storing Multiple Data Types
javascript
// Storing different types of data
const appData = {
  todos: [
    {id: 1, text: "Learn JavaScript", completed: true}
  ],
  settings: {
    theme: "dark",
    notifications: true
  },
  user: {
    name: "Alex",
    lastLogin: new Date().toISOString()
  }
};

localStorage.setItem('appData', JSON.stringify(appData));
Data Migration and Versioning

Handle changes to your data structure over time:

javascript
function loadTodosWithMigration() {
  const stored = localStorage.getItem('todos');
  
  if (!stored) return [];
  
  try {
    const todos = JSON.parse(stored);
    
    // Migrate old format to new format
    return todos.map(todo => {
      // Add missing properties for old todos
      if (!todo.createdAt) {
        todo.createdAt = new Date().toISOString();
      }
      if (!todo.id) {
        todo.id = Date.now().toString(36) + Math.random().toString(36).substr(2);
      }
      return todo;
    });
  } catch (error) {
    console.error('Migration failed:', error);
    return [];
  }
}
Data Compression for Large Datasets

When storing large amounts of data:

javascript
// Simple compression example
function compressData(data) {
  const jsonString = JSON.stringify(data);
  // Remove unnecessary whitespace
  return jsonString.replace(/\s+/g, ' ');
}

function saveLargeData(key, data) {
  const compressed = compressData(data);
  
  // Check storage size
  const size = new Blob([compressed]).size;
  console.log(`Data size: ${(size / 1024).toFixed(2)} KB`);
  
  localStorage.setItem(key, compressed);
}
Implementing Data Export/Import

Allow users to backup and restore their data:

javascript
// Export data as JSON file
function exportData() {
  const data = localStorage.getItem('todos');
  const blob = new Blob([data], {type: 'application/json'});
  const url = URL.createObjectURL(blob);
  
  const link = document.createElement('a');
  link.href = url;
  link.download = `todos-backup-${Date.now()}.json`;
  link.click();
  
  URL.revokeObjectURL(url);
}

// Import data from JSON file
function importData(file) {
  const reader = new FileReader();
  
  reader.onload = function(event) {
    try {
      const data = JSON.parse(event.target.result);
      localStorage.setItem('todos', JSON.stringify(data));
      alert('Data imported successfully!');
      location.reload();
    } catch (error) {
      alert('Invalid file format!');
    }
  };
  
  reader.readAsText(file);
}

Frequently Asked Questions

localStorage stores larger client-side data without being sent to the server, making it fast and persistent, while cookies are smaller, sent with every request, and commonly used for sessions and authentication.

While you technically can store base64-encoded images in localStorage, it's not recommended due to the 5-10MB limit

Use the storage event to sync data across tabs

Yes, users can disable localStorage through browser settings or private browsing mode