{
  "id": "Tpn2kZa5UpfsPsiQ",
  "meta": {
    "instanceId": "43905f7eef4dcd9dab6e186fbccfa78b0e88a07b5e6c19ebd17391441f834f49",
    "templateCredsSetupCompleted": true
  },
  "name": "Build a Redis-Powered CRUD App with HTML Frontend",
  "tags": [],
  "nodes": [
    {
      "id": "9f64f47f-c361-433a-b728-feb3af032b22",
      "name": "Hook: Get Items",
      "type": "n8n-nodes-base.webhook",
      "position": [
        940,
        100
      ],
      "webhookId": "e33c4cf1-bff1-4175-ab94-9099c3ab50d8",
      "parameters": {
        "path": "items",
        "options": {},
        "responseData": "allEntries",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "fabd86e8-61a2-43e0-8ad8-e9ee54cc6707",
      "name": "Hook: Add Item",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1700,
        80
      ],
      "webhookId": "e33c4cf1-bff1-4175-ab94-9099c3ab50d8",
      "parameters": {
        "path": "=items",
        "options": {},
        "httpMethod": "POST",
        "responseData": "allEntries",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "cc1627f9-cad2-43fe-b295-2cddbaf22688",
      "name": "Add Item",
      "type": "n8n-nodes-base.redis",
      "position": [
        2120,
        80
      ],
      "parameters": {
        "key": "={{ $workflow.id }}--items-{{ $json['Tpn2kZa5UpfsPsiQ--itemid'] }}",
        "value": "={{ $('Hook: Add Item').item.json.body.name }}",
        "operation": "set"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "55617872-2745-4571-b03f-9d1b6e4546a1",
      "name": "GetID",
      "type": "n8n-nodes-base.redis",
      "position": [
        1920,
        80
      ],
      "parameters": {
        "key": "={{$workflow.id}}--itemid",
        "operation": "incr"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "cf4728d6-e5df-4fbc-aa03-5b9f6b35ae13",
      "name": "Hook: Edit Item",
      "type": "n8n-nodes-base.webhook",
      "position": [
        940,
        580
      ],
      "webhookId": "e33c4cf1-bff1-4175-ab94-9099c3ab50d8",
      "parameters": {
        "path": "=items",
        "options": {},
        "httpMethod": "PUT",
        "responseData": "allEntries",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "06ec7492-c178-4b28-a13f-09a34b682e82",
      "name": "Hook: Delete Item",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1700,
        580
      ],
      "webhookId": "e33c4cf1-bff1-4175-ab94-9099c3ab50d8",
      "parameters": {
        "path": "=items",
        "options": {},
        "httpMethod": "DELETE",
        "responseData": "allEntries",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "a6e8b92d-e978-445b-b170-f38a869d6176",
      "name": "Delete Item by Id",
      "type": "n8n-nodes-base.redis",
      "position": [
        1920,
        580
      ],
      "parameters": {
        "key": "={{ $workflow.id }}--items-{{ $json.body.id }}",
        "operation": "delete"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1735de84-7b3e-44e4-95e1-685d3cd196a2",
      "name": "Hook: Reset",
      "type": "n8n-nodes-base.webhook",
      "position": [
        920,
        1080
      ],
      "webhookId": "e33c4cf1-bff1-4175-ab94-9099c3ab50d8",
      "parameters": {
        "path": "items-reset",
        "options": {},
        "responseData": "allEntries",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "6c0e7c1e-cab6-4551-be03-f75f09275ebd",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1860,
        980
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "1517abbf-deba-4854-bb57-a37e6d4ba446",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        120,
        -180
      ],
      "parameters": {
        "width": 700,
        "height": 1120,
        "content": "## 1. Render the HTML CRUD App\n\nThis webhook serves a self-contained HTML Single Page Application (SPA) for basic CRUD operations. The HTML content is returned directly in the webhook response.\n\nThis setup is ideal for lightweight, browser-based tools without external hosting.\n\n### How to Use\n- Open the webhook URL in a browser\n- The CRUD interface will load and connect to the data source via API calls\n- Before using, make sure to edit the `api_url` in the `SET API URL` node to match your webhook endpoint\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n![App Screenshot](https://raw.githubusercontent.com/Ficky-Dev/images/refs/heads/main/Simple%20n8n%20Redis%20CRUD%20App.png)\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d5efb911-029f-4d51-9412-8fd82f694487",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1620,
        -180
      ],
      "parameters": {
        "width": 720,
        "height": 480,
        "content": "## 2b. REST API: Add Item\n\nThis webhook handles the **Add Item** functionality.\nThis endpoint is typically called by the HTML CRUD App when adding a new item.\n- **Method**: `POST`\n- **Request Body**: `{ \"name\": \"item name\" }`\n- **Function**: Generates an auto-incremented ID using Redis and saves the data under that ID\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c1be97a7-e86d-4994-a361-5fbe9ed0f09b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -180
      ],
      "parameters": {
        "color": 4,
        "width": 480,
        "height": 1120,
        "content": "# Build a Redis-Powered CRUD App with HTML Frontend\n\nThis workflow demonstrates how to use **n8n** to build a complete, self-contained CRUD (Create, Read, Update, Delete) application without relying on any external server or hosting. It not only acts as the **backend**, handling all CRUD operations through **Webhook endpoints**, but also **serves a fully functional HTML Single Page Application (SPA)** directly via a webhook response.\n\n**Redis** is used as a lightweight data store, providing fast and simple key-value storage with auto-incremented IDs. Because both the frontend (HTML app) and backend (API endpoints) are managed entirely within a single n8n workflow, you can quickly prototype or deploy small tools without additional infrastructure.\n\nThis approach is ideal for:\n- Rapidly creating no-code or low-code applications\n- Running fully browser-based tools served directly from n8n\n- Teaching or demonstrating n8n + Redis integration in a single workflow\n\n\n## Features\n- Add new item with **auto-incremented ID**\n- Edit existing item\n- Delete specific item\n- Reset all data (clear storage and reset autoincrement id)\n- Single HTML frontend for demonstration (no framework required)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9306f3b1-8cff-4c32-952b-913805f5f21f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        860,
        340
      ],
      "parameters": {
        "width": 720,
        "height": 420,
        "content": "## 2c. REST API: Edit Item\n\nThis webhook handles updating an existing item in Redis.\n\n- **Method**: `PUT`\n- **Request Body**: `{ \"id\": 1, \"name\": \"Updated Item Name\" }`\n- **Function**: Finds the item by the given `id` and updates its data in Redis"
      },
      "typeVersion": 1
    },
    {
      "id": "e4255301-09dc-4b9f-ac8e-137770e1544c",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1620,
        340
      ],
      "parameters": {
        "width": 720,
        "height": 420,
        "content": "## 2d. REST API: Delete Item\n\nThis webhook handles deleting a specific item from Redis.\n\n- **Method**: `DELETE`\n- **Request Body**: `{ \"id\": 1 }`\n- **Function**: Removes the item with the given `id` from Redis\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1a30ae5d-7dd0-45fc-a67c-24c3276e04b2",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        860,
        -180
      ],
      "parameters": {
        "width": 720,
        "height": 480,
        "content": "## 2a. REST API: Get All Items\n\nThis webhook handles retrieving all saved items from Redis.\nEach item is returned with its corresponding ID and associated data (e.g., name). This endpoint is used by the HTML CRUD App to display the full list of items.\n\n- **Method**: `GET`\n- **Function**: Fetches all items stored in Redis and returns them as a JSON array\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "332adc9e-5798-4099-afe7-f318cc313e66",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        860,
        800
      ],
      "parameters": {
        "width": 1480,
        "height": 460,
        "content": "## 2e. REST API: Reset Items\n\nThis webhook handles resetting all data in the application.\n\n- **Method**: `POST`\n- **Function**:\n  - Deletes all stored items from Redis\n  - Resets the auto-increment ID by deleting the data in Redis\n"
      },
      "typeVersion": 1
    },
    {
      "id": "7b7c6b97-5eef-45f1-a9eb-5a3b781f7048",
      "name": "SET API URL",
      "type": "n8n-nodes-base.set",
      "position": [
        400,
        180
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "7f96b73c-f1b5-44d5-aaf8-a2f720bdb2e7",
              "name": "API_URL",
              "type": "string",
              "value": "SET YOUR API URL USING THE WEBHOOK FROM"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6c459c32-a944-4000-877a-8a1c47af2129",
      "name": "Format as JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        1660,
        980
      ],
      "parameters": {
        "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nvar items = [];\nfor (const key in $input.first().json) {\n  var name = $input.first().json[key]\n  var id = parseInt(key.split('--')[1].split('-')[1]);\n  items.push({id, name});\n}\nreturn items;"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "5e641c92-3453-45f4-9601-fc446476dfcf",
      "name": "Get All Items",
      "type": "n8n-nodes-base.redis",
      "position": [
        1160,
        100
      ],
      "parameters": {
        "operation": "keys",
        "keyPattern": "={{ $workflow.id }}--items*"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "26498605-4219-43cb-8277-23858f48f9ae",
      "name": "Format the Items",
      "type": "n8n-nodes-base.code",
      "position": [
        1380,
        100
      ],
      "parameters": {
        "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nvar items = [];\nfor (const key in $input.first().json) {\n  var name = $input.first().json[key]\n  var id = parseInt(key.split('--')[1].split('-')[1]);\n  items.push({id, name});\n}\nreturn items;"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "d928736e-2d9d-4283-ab97-ce6699114c4c",
      "name": "Update the item",
      "type": "n8n-nodes-base.redis",
      "position": [
        1180,
        580
      ],
      "parameters": {
        "key": "={{ $workflow.id }}--items-{{ $json.body.id }}",
        "value": "={{ $json.body.name }}",
        "operation": "set"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1ddcd613-115d-483a-9428-9023aeb8d8bb",
      "name": "Get All Item Keys",
      "type": "n8n-nodes-base.redis",
      "position": [
        1360,
        1080
      ],
      "parameters": {
        "operation": "keys",
        "keyPattern": "={{ $workflow.id }}--items*"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "39359400-3722-41f1-8341-d905bb868ae7",
      "name": "Delete an Item",
      "type": "n8n-nodes-base.redis",
      "position": [
        2100,
        1000
      ],
      "parameters": {
        "key": "={{ $workflow.id }}--items-{{ $json.id }}",
        "operation": "delete"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f182ae6a-8fab-4da2-a416-33e3951b482b",
      "name": "Reset Autoincrement ID",
      "type": "n8n-nodes-base.redis",
      "position": [
        1140,
        1080
      ],
      "parameters": {
        "key": "={{$workflow.id}}--itemid",
        "operation": "delete"
      },
      "credentials": {
        "redis": {
          "id": "credential-id",
          "name": "redis Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "02d53c31-fbb1-4fac-a407-922c3364f45b",
      "name": "Serve HTML CRUD App",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        620,
        180
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "=<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Simple N8N Redis CRUD App</title>\n  <style>\n    * {\n      margin: 0;\n      padding: 0;\n      box-sizing: border-box;\n    }\n\n    body {\n      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n      background: #f3f3f3;\n      min-height: 100vh;\n      padding: 2rem;\n      color: #333;\n    }\n\n    .container {\n      max-width: 800px;\n      margin: 0 auto;\n      background: white;\n      border-radius: 16px;\n      box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);\n      overflow: hidden;\n    }\n\n    .header {\n      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n      color: white;\n      padding: 2rem;\n      text-align: center;\n    }\n\n    .header h1 {\n      font-size: 2.5rem;\n      font-weight: 300;\n      margin-bottom: 0.5rem;\n    }\n\n    .content {\n      padding: 2rem;\n    }\n\n    .form-container {\n      background: #f8f9fa;\n      border-radius: 12px;\n      padding: 1.5rem;\n      margin-bottom: 2rem;\n      border: 1px solid #e9ecef;\n    }\n\n    #item-form {\n      display: flex;\n      gap: 1rem;\n      align-items: center;\n      flex-wrap: wrap;\n    }\n\n    #item-id {\n      display: none;\n    }\n\n    #item-name {\n      flex: 1;\n      min-width: 200px;\n      padding: 0.75rem 1rem;\n      border: 2px solid #e9ecef;\n      border-radius: 8px;\n      font-size: 1rem;\n      transition: all 0.3s ease;\n      background: white;\n    }\n\n    #item-name:focus {\n      outline: none;\n      border-color: #667eea;\n      box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);\n    }\n\n    button {\n      padding: 0.75rem 1.5rem;\n      border: none;\n      border-radius: 8px;\n      font-size: 1rem;\n      font-weight: 500;\n      cursor: pointer;\n      transition: all 0.3s ease;\n      text-transform: uppercase;\n      letter-spacing: 0.5px;\n    }\n\n    button[type=\"submit\"] {\n      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n      color: white;\n    }\n\n    button[type=\"submit\"]:hover {\n      transform: translateY(-2px);\n      box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);\n    }\n\n    #cancel-edit {\n      background: #6c757d;\n      color: white;\n    }\n\n    #cancel-edit:hover {\n      background: #5a6268;\n      transform: translateY(-2px);\n    }\n\n    .error {\n      background: #f8d7da;\n      color: #721c24;\n      padding: 1rem;\n      border-radius: 8px;\n      margin-bottom: 1rem;\n      border: 1px solid #f5c6cb;\n      display: none;\n    }\n\n    .error:not(:empty) {\n      display: block;\n    }\n\n    .table-container {\n      background: white;\n      border-radius: 12px;\n      overflow: hidden;\n      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);\n      border: 1px solid #e9ecef;\n    }\n\n    table {\n      width: 100%;\n      border-collapse: collapse;\n    }\n\n    th {\n      background: #f8f9fa;\n      padding: 1rem;\n      text-align: left;\n      font-weight: 600;\n      color: #495057;\n      border-bottom: 2px solid #e9ecef;\n      text-transform: uppercase;\n      font-size: 0.875rem;\n      letter-spacing: 0.5px;\n    }\n\n    td {\n      padding: 1rem;\n      border-bottom: 1px solid #e9ecef;\n      vertical-align: middle;\n    }\n\n    tr:hover {\n      background: #f8f9fa;\n    }\n\n    .action-buttons {\n      display: flex;\n      gap: 0.5rem;\n    }\n\n    .btn-edit {\n      background: #17a2b8;\n      color: white;\n      padding: 0.5rem 1rem;\n      font-size: 0.875rem;\n    }\n\n    .btn-edit:hover {\n      background: #138496;\n      transform: translateY(-1px);\n    }\n\n    .btn-delete {\n      background: #dc3545;\n      color: white;\n      padding: 0.5rem 1rem;\n      font-size: 0.875rem;\n    }\n\n    .btn-delete:hover {\n      background: #c82333;\n      transform: translateY(-1px);\n    }\n\n    .empty-state {\n      text-align: center;\n      padding: 3rem;\n      color: #6c757d;\n    }\n\n    .empty-state p {\n      font-size: 1.1rem;\n      margin-bottom: 1rem;\n    }\n\n    @media (max-width: 768px) {\n      body {\n        padding: 1rem;\n      }\n      \n      .header h1 {\n        font-size: 2rem;\n      }\n      \n      #item-form {\n        flex-direction: column;\n        align-items: stretch;\n      }\n      \n      #item-name {\n        min-width: auto;\n      }\n      \n      .action-buttons {\n        flex-direction: column;\n      }\n      \n      th, td {\n        padding: 0.75rem 0.5rem;\n      }\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>Simple N8N Redis CRUD App</h1>\n    </div>\n    <div class=\"content\">\n      <div class=\"form-container\">\n        <form id=\"item-form\">\n          <input type=\"hidden\" id=\"item-id\">\n          <input type=\"text\" id=\"item-name\" placeholder=\"Enter item name...\" required>\n          <button type=\"submit\">Add Item</button>\n          <button type=\"button\" id=\"cancel-edit\" style=\"display:none;\">Cancel</button>\n        </form>\n        <div style=\"margin-top: 1rem; text-align: center;\">\n          <button type=\"button\" id=\"reset-btn\" style=\"background: #dc3545; color: white;\">Reset All Items</button>\n        </div>\n      </div>\n      <div class=\"error\" id=\"error\"></div>\n      <div class=\"table-container\">\n        <table>\n          <thead>\n            <tr><th>ID</th><th>Name</th><th>Actions</th></tr>\n          </thead>\n          <tbody id=\"items-tbody\">\n            <tr>\n              <td colspan=\"3\" class=\"empty-state\">\n                <p>No items found</p>\n                <p>Add your first item using the form above</p>\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n  <script>\n    const API_URL = '{{ $json.API_URL }}';\n    const form = document.getElementById('item-form');\n    const itemIdInput = document.getElementById('item-id');\n    const itemNameInput = document.getElementById('item-name');\n    const cancelEditBtn = document.getElementById('cancel-edit');\n    const errorDiv = document.getElementById('error');\n    const tbody = document.getElementById('items-tbody');\n    const resetBtn = document.getElementById('reset-btn');\n\n    function showError(msg) {\n      errorDiv.textContent = msg;\n      setTimeout(() => { errorDiv.textContent = ''; }, 5000);\n    }\n\n    function renderItems(items) {\n      tbody.innerHTML = '';\n      if (items.length === 0 || !items[0].id) {\n        tbody.innerHTML = `\n          <tr>\n            <td colspan=\"3\" class=\"empty-state\">\n              <p>No items found</p>\n              <p>Add your first item using the form above</p>\n            </td>\n          </tr>\n        `;\n        return;\n      }\n     \n      items.forEach(item => {\n        const tr = document.createElement('tr');\n        tr.innerHTML = `\n          <td><strong>#${item.id}</strong></td>\n          <td>${item.name}</td>\n          <td>\n            <div class=\"action-buttons\">\n              <button class=\"btn-edit\" onclick=\"editItem(${item.id}, '${item?.name?.replace(/'/g, \"&#39;\")}')\">Edit</button>\n              <button class=\"btn-delete\" onclick=\"deleteItem(${item.id})\">Delete</button>\n            </div>\n          </td>\n        `;\n        tbody.appendChild(tr);\n      });\n    }\n\n    async function fetchItems() {\n      try {\n        const res = await fetch(API_URL);\n        if (!res.ok) throw new Error('Failed to fetch items');\n        const items = await res.json();\n        renderItems(items);\n      } catch (err) {\n        showError(err.message);\n      }\n    }\n\n    form.onsubmit = async (e) => {\n      e.preventDefault();\n      const id = itemIdInput.value;\n      const name = itemNameInput.value.trim();\n      if (!name) return;\n      try {\n        if (id) {\n          // Update\n          const res = await fetch(`${API_URL}`, {\n            method: 'PUT',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ id, name })\n          });\n          if (!res.ok) throw new Error('Failed to update item');\n        } else {\n          // Create\n          const res = await fetch(API_URL, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ name })\n          });\n          if (!res.ok) throw new Error('Failed to add item');\n        }\n        form.reset();\n        itemIdInput.value = '';\n        form.querySelector('button[type=\"submit\"]').textContent = 'Add Item';\n        cancelEditBtn.style.display = 'none';\n        await fetchItems();\n      } catch (err) {\n        showError(err.message);\n      }\n    };\n\n    window.editItem = (id, name) => {\n      itemIdInput.value = id;\n      itemNameInput.value = name;\n      form.querySelector('button[type=\"submit\"]').textContent = 'Update Item';\n      cancelEditBtn.style.display = '';\n    };\n\n    cancelEditBtn.onclick = () => {\n      form.reset();\n      itemIdInput.value = '';\n      form.querySelector('button[type=\"submit\"]').textContent = 'Add Item';\n      cancelEditBtn.style.display = 'none';\n    };\n\n    window.deleteItem = async (id) => {\n      if (!confirm('Delete this item?')) return;\n      try {\n        const res = await fetch(`${API_URL}`, { \n          method: 'DELETE',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify({ id })\n        });\n        if (!res.ok) throw new Error('Failed to delete item');\n        await fetchItems();\n      } catch (err) {\n        showError(err.message);\n      }\n    };\n\n    // Initial load\n    fetchItems();\n\n    // Reset functionality\n    async function resetItems() {\n      if (!confirm('Are you sure you want to reset all items? This action cannot be undone.')) return;\n      try {\n        const res = await fetch(`${API_URL}-reset`, { method: 'GET' });\n        if (!res.ok) throw new Error('Failed to reset items');\n        await fetchItems();\n      } catch (err) {\n        showError(err.message);\n      }\n    }\n\n    resetBtn.onclick = resetItems;\n  </script>\n</body>\n</html> "
      },
      "typeVersion": 1.4
    },
    {
      "id": "f1061585-366d-4586-b755-6bfb7b5da0be",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -520,
        -180
      ],
      "parameters": {
        "color": 4,
        "width": 620,
        "height": 1120,
        "content": "# Setup Instructions\n\n## 1. Prerequisites\nBefore importing and running the workflow, make sure you have:\n- A running **n8n** instance (self-hosted or cloud)\n- A running **Redis** server (local or remote)\n\n## 2. API Path Setup\n\nFor the REST API, use a consistent `path`. For example, if you choose `items` as the path:\n\n- **2a. Get All Items**  \n  - **Method**: `GET`  \n  - **Endpoint**: `items`\n\n- **2b. Add Item**  \n  - **Method**: `POST`  \n  - **Endpoint**: `items`\n\n- **2c. Edit Item**  \n  - **Method**: `PUT`  \n  - **Endpoint**: `items`\n\n- **2d. Delete Item**  \n  - **Method**: `DELETE`  \n  - **Endpoint**: `items`\n\n- **2e. Reset Items**  \n  - **Method**: `POST`  \n  - **Endpoint**: `items-reset`\n\n\n## 3. Configure the API URL\n\nSet the `API URL` in the **SET API URL** node. Use your n8n webhook URL, for example:  \n`https://yourn8n.com/webhook/items`\n\n## 4. Run the HTML App\n\nOnce everything is set:\n1. Open the webhook URL for the HTML app in a browser.\n2. The CRUD interface will load and connect to the API endpoints automatically.\n3. You can now add, edit, delete, or reset items directly from the web interface.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3e5a016c-d1cd-4155-bce5-e1904871ea5b",
      "name": "Webhook: Serve HTML App",
      "type": "n8n-nodes-base.webhook",
      "position": [
        180,
        180
      ],
      "webhookId": "e33c4cf1-bff1-4175-ab94-9099c3ab50d8",
      "parameters": {
        "path": "simple-crud",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    }
  ],
  "active": true,
  "pinData": {
    "Hook: Reset": [
      {
        "json": {
          "body": {},
          "query": {},
          "params": {},
          "headers": {
            "host": "n8n.backstage.my.id",
            "accept": "*/*",
            "cf-ray": "95a5e56b9b5797a7-SIN",
            "origin": "http://127.0.0.1:3000",
            "referer": "http://127.0.0.1:3000/",
            "cdn-loop": "cloudflare; loops=1",
            "priority": "u=1, i",
            "sec-ch-ua": "\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\"",
            "x-real-ip": "104.23.175.58",
            "cf-visitor": "{\"scheme\":\"https\"}",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
            "cf-ipcountry": "ID",
            "if-none-match": "W/\"4-TplQofIwX1bTWMrSPyggP7Oqy+8\"",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "cross-site",
            "accept-encoding": "gzip, br",
            "accept-language": "en-US,en;q=0.9,id;q=0.8",
            "x-forwarded-for": "104.23.175.58",
            "cf-connecting-ip": "182.253.122.4",
            "sec-ch-ua-mobile": "?0",
            "x-forwarded-host": "n8n.backstage.my.id",
            "x-forwarded-port": "443",
            "x-forwarded-proto": "https",
            "sec-ch-ua-platform": "\"Windows\"",
            "x-forwarded-server": "b95f80258063"
          },
          "webhookUrl": "https://n8n.backstage.my.id/webhook/items",
          "executionMode": "production"
        }
      }
    ],
    "Hook: Add Item": [
      {
        "json": {
          "body": {
            "name": "Ficky"
          },
          "query": {},
          "params": {},
          "headers": {
            "host": "n8n.backstage.my.id",
            "accept": "*/*",
            "cf-ray": "95a547533fa540d2-SIN",
            "cdn-loop": "cloudflare; loops=1",
            "x-real-ip": "162.158.106.96",
            "cf-visitor": "{\"scheme\":\"https\"}",
            "user-agent": "PostmanRuntime/7.44.1",
            "cf-ipcountry": "ID",
            "content-type": "application/json",
            "cache-control": "no-cache",
            "postman-token": "b1ada9f3-8a6f-49b9-98a9-efad1c869b26",
            "content-length": "25",
            "accept-encoding": "gzip, br",
            "x-forwarded-for": "162.158.106.96",
            "cf-connecting-ip": "182.253.122.4",
            "x-forwarded-host": "n8n.backstage.my.id",
            "x-forwarded-port": "443",
            "x-forwarded-proto": "https",
            "x-forwarded-server": "b95f80258063"
          },
          "webhookUrl": "https://n8n.backstage.my.id/webhook/items",
          "executionMode": "production"
        }
      }
    ],
    "Hook: Edit Item": [
      {
        "json": {
          "body": {
            "id": "14",
            "name": "asdf123"
          },
          "query": {},
          "params": {},
          "headers": {
            "host": "n8n.backstage.my.id",
            "accept": "*/*",
            "cf-ray": "95a5ef1fea6097a7-SIN",
            "origin": "http://127.0.0.1:3000",
            "referer": "http://127.0.0.1:3000/",
            "cdn-loop": "cloudflare; loops=1",
            "priority": "u=1, i",
            "sec-ch-ua": "\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\"",
            "x-real-ip": "104.23.175.58",
            "cf-visitor": "{\"scheme\":\"https\"}",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
            "cf-ipcountry": "ID",
            "content-type": "application/json",
            "content-length": "28",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "cross-site",
            "accept-encoding": "gzip, br",
            "accept-language": "en-US,en;q=0.9,id;q=0.8",
            "x-forwarded-for": "104.23.175.58",
            "cf-connecting-ip": "182.253.122.4",
            "sec-ch-ua-mobile": "?0",
            "x-forwarded-host": "n8n.backstage.my.id",
            "x-forwarded-port": "443",
            "x-forwarded-proto": "https",
            "sec-ch-ua-platform": "\"Windows\"",
            "x-forwarded-server": "b95f80258063"
          },
          "webhookUrl": "https://n8n.backstage.my.id/webhook/items",
          "executionMode": "production"
        }
      }
    ],
    "Hook: Get Items": [
      {
        "json": {
          "body": {},
          "query": {},
          "params": {},
          "headers": {
            "host": "n8n.backstage.my.id",
            "accept": "*/*",
            "cf-ray": "95a5e56b9b5797a7-SIN",
            "origin": "http://127.0.0.1:3000",
            "referer": "http://127.0.0.1:3000/",
            "cdn-loop": "cloudflare; loops=1",
            "priority": "u=1, i",
            "sec-ch-ua": "\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\"",
            "x-real-ip": "104.23.175.58",
            "cf-visitor": "{\"scheme\":\"https\"}",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
            "cf-ipcountry": "ID",
            "if-none-match": "W/\"4-TplQofIwX1bTWMrSPyggP7Oqy+8\"",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "cross-site",
            "accept-encoding": "gzip, br",
            "accept-language": "en-US,en;q=0.9,id;q=0.8",
            "x-forwarded-for": "104.23.175.58",
            "cf-connecting-ip": "182.253.122.4",
            "sec-ch-ua-mobile": "?0",
            "x-forwarded-host": "n8n.backstage.my.id",
            "x-forwarded-port": "443",
            "x-forwarded-proto": "https",
            "sec-ch-ua-platform": "\"Windows\"",
            "x-forwarded-server": "b95f80258063"
          },
          "webhookUrl": "https://n8n.backstage.my.id/webhook/items",
          "executionMode": "production"
        }
      }
    ],
    "Hook: Delete Item": [
      {
        "json": {
          "body": {
            "name": "Ficky"
          },
          "query": {},
          "params": {},
          "headers": {
            "host": "n8n.backstage.my.id",
            "accept": "*/*",
            "cf-ray": "95a547533fa540d2-SIN",
            "cdn-loop": "cloudflare; loops=1",
            "x-real-ip": "162.158.106.96",
            "cf-visitor": "{\"scheme\":\"https\"}",
            "user-agent": "PostmanRuntime/7.44.1",
            "cf-ipcountry": "ID",
            "content-type": "application/json",
            "cache-control": "no-cache",
            "postman-token": "b1ada9f3-8a6f-49b9-98a9-efad1c869b26",
            "content-length": "25",
            "accept-encoding": "gzip, br",
            "x-forwarded-for": "162.158.106.96",
            "cf-connecting-ip": "182.253.122.4",
            "x-forwarded-host": "n8n.backstage.my.id",
            "x-forwarded-port": "443",
            "x-forwarded-proto": "https",
            "x-forwarded-server": "b95f80258063"
          },
          "webhookUrl": "https://n8n.backstage.my.id/webhook/items",
          "executionMode": "production"
        }
      }
    ]
  },
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e1c11987-de15-4858-8cf9-4568567b3078",
  "connections": {
    "GetID": {
      "main": [
        [
          {
            "node": "Add Item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Item": {
      "main": [
        []
      ]
    },
    "Hook: Reset": {
      "main": [
        [
          {
            "node": "Reset Autoincrement ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SET API URL": {
      "main": [
        [
          {
            "node": "Serve HTML CRUD App",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All Items": {
      "main": [
        [
          {
            "node": "Format the Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete an Item": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format as JSON": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hook: Add Item": {
      "main": [
        [
          {
            "node": "GetID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hook: Edit Item": {
      "main": [
        [
          {
            "node": "Update the item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hook: Get Items": {
      "main": [
        [
          {
            "node": "Get All Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Delete an Item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All Item Keys": {
      "main": [
        [
          {
            "node": "Format as JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hook: Delete Item": {
      "main": [
        [
          {
            "node": "Delete Item by Id",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reset Autoincrement ID": {
      "main": [
        [
          {
            "node": "Get All Item Keys",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook: Serve HTML App": {
      "main": [
        [
          {
            "node": "SET API URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}