<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ streaming - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ streaming - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 26 Jun 2026 17:31:23 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/streaming/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Reliable SSE Client in TypeScript ]]>
                </title>
                <description>
                    <![CDATA[ When you build a feature that streams data, like an AI chat response or a live notification feed, the network is rarely as cooperative as fetch makes it look. Connections drop, proxies buffer response ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-reliable-sse-client-in-typescript/</link>
                <guid isPermaLink="false">6a3db0651016f6a6b4bd2a89</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ streaming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SSE ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ timothy ogbemudia ]]>
                </dc:creator>
                <pubDate>Thu, 25 Jun 2026 22:49:09 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3c13d795-15e8-452a-b490-89528d58efd2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you build a feature that streams data, like an AI chat response or a live notification feed, the network is rarely as cooperative as <code>fetch</code> makes it look.</p>
<p>Connections drop, proxies buffer responses, and mobile networks switch from WiFi to cellular mid-stream. If your streaming code doesn't plan for this, the user sees a response that just stops, with no error and no recovery.</p>
<p>In this article, you'll use an open source TypeScript library called <a href="https://github.com/glamboyosa/ore">Ore</a> as a practical example of how to build a streaming client that handles real-world network conditions: automatic retries, the official Server-Sent Events (SSE) parsing spec, and clean integration with React and React Server Components.</p>
<p>By the end, you'll understand how async generators, the Fetch API, and the SSE spec fit together to build something far more reliable than a basic <code>fetch</code> and <code>response.body.getReader()</code> loop.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-you-will-learn">What You Will Learn</a></p>
</li>
<li><p><a href="#heading-what-is-server-sent-events">What Is Server-Sent Events?</a></p>
</li>
<li><p><a href="#heading-why-build-a-custom-streaming-client">Why Build a Custom Streaming Client?</a></p>
</li>
<li><p><a href="#heading-how-to-stream-raw-chunks-with-an-async-generator">How to Stream Raw Chunks with an Async Generator</a></p>
</li>
<li><p><a href="#heading-how-to-parse-server-sent-events-by-hand">How to Parse Server-Sent Events by Hand</a></p>
</li>
<li><p><a href="#heading-how-to-implement-reconnection-with-last-event-id">How to Implement Reconnection with Last-Event-ID</a></p>
</li>
<li><p><a href="#heading-how-to-handle-retries-with-backoff">How to Handle Retries with Backoff</a></p>
</li>
<li><p><a href="#heading-how-to-use-this-with-react">How to Use This with React</a></p>
</li>
<li><p><a href="#heading-how-to-use-this-with-react-server-components">How to Use This with React Server Components</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along, you should have:</p>
<ul>
<li><p>A working understanding of TypeScript</p>
</li>
<li><p>Familiarity with <code>fetch</code>, <code>ReadableStream</code>, and <code>async</code>/<code>await</code></p>
</li>
<li><p>Basic knowledge of React (for the React-specific sections)</p>
</li>
</ul>
<h2 id="heading-what-you-will-learn">What You Will Learn</h2>
<ul>
<li><p>How to stream raw text or bytes from a <code>fetch</code> response using async generators</p>
</li>
<li><p>How to parse the Server-Sent Events spec by hand, field by field</p>
</li>
<li><p>How to implement automatic reconnection with <code>Last-Event-ID</code> so you don't lose events</p>
</li>
<li><p>How to handle retries with exponential backoff</p>
</li>
<li><p>How to integrate a streaming client with React state and React Server Components</p>
</li>
</ul>
<h2 id="heading-what-is-server-sent-events">What Is Server-Sent Events?</h2>
<p>Server-Sent Events (SSE) is a web standard for one-way streaming from server to client over a single HTTP connection. Unlike WebSockets, it's plain HTTP, which means it works through existing infrastructure like load balancers and proxies without special configuration.</p>
<p>An SSE response looks like this on the wire:</p>
<pre><code class="language-plaintext">event: update
id: 42
data: {"status": "processing"}

event: update
id: 43
data: {"status": "complete"}
</code></pre>
<p>Each event is separated by a blank line. The <code>data</code> field carries the payload, <code>event</code> names the event type, and <code>id</code> lets the client track its position in the stream for reconnection.</p>
<p>The browser has a built-in <code>EventSource</code> API for this, but it has real limitations: no custom headers, no POST requests, and inconsistent reconnection behavior across browsers. For anything beyond the simplest case, you often need to parse the stream yourself.</p>
<h2 id="heading-why-build-a-custom-streaming-client">Why Build a Custom Streaming Client?</h2>
<p>Many streaming use cases, like AI chat responses, don't use the SSE spec at all. They're just raw chunks of text arriving over time. Other cases, like live notifications, genuinely benefit from the structure SSE provides: named events, IDs for resumption, and a server-controlled retry interval.</p>
<p>Ore handles both with two separate functions:</p>
<ul>
<li><p><code>stream()</code> for raw text or byte streaming, with no assumptions about format</p>
</li>
<li><p><code>streamSSE()</code> for spec-compliant SSE parsing</p>
</li>
</ul>
<p>Both are async generators, so consuming either looks the same from the call site:</p>
<pre><code class="language-typescript">for await (const chunk of stream("https://api.example.com/chat")) {
  console.log(chunk);
}
</code></pre>
<h2 id="heading-how-to-stream-raw-chunks-with-an-async-generator">How to Stream Raw Chunks with an Async Generator</h2>
<p>The simplest case is streaming raw text. This is useful for AI responses or log tails where there's no event structure, just a sequence of bytes arriving over time.</p>
<p>Here's the core of <code>stream()</code>:</p>
<pre><code class="language-typescript">export async function* stream(
  url: string,
  options?: StreamOptions
): AsyncGenerator&lt;string | Uint8Array, void, unknown&gt; {
  const { headers, retries = 3, signal, decode = true } = options || {};

  let retryCount = 0;

  while (retryCount &lt;= retries) {
    try {
      const response = await fetch(url, { method: "GET", headers, signal });

      if (!response.body) {
        throw new Error("Response body is null");
      }

      const reader = response.body.getReader();
      const decoder = new TextDecoder();

      try {
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          yield decode ? decoder.decode(value, { stream: true }) : value;
        }
      } finally {
        reader.releaseLock();
      }

      return;
    } catch (error: any) {
      if (signal?.aborted) throw error;
      retryCount++;
      if (retryCount &gt; retries) {
        throw new Error(`Max retries exceeded. Last error: ${error.message}`);
      }
      await new Promise((r) =&gt; setTimeout(r, 1000 * retryCount));
    }
  }
}
</code></pre>
<p>A few design decisions are worth calling out.</p>
<p>The function is an async generator (<code>async function*</code>), so the caller can use <code>for await...of</code> instead of managing a reader and a loop manually. That's the difference between exposing a raw <code>ReadableStream</code> and exposing something pleasant to consume.</p>
<p>The <code>finally</code> block always releases the reader lock, even if the loop exits early through a <code>break</code> or an exception. Forgetting this is a common source of stream leaks.</p>
<p>The retry loop only catches errors from the <code>fetch</code> call and the read loop. If the <code>AbortSignal</code> was the cause of the failure, it rethrows immediately rather than retrying, since retrying a deliberate cancellation makes no sense.</p>
<h2 id="heading-how-to-parse-server-sent-events-by-hand">How to Parse Server-Sent Events by Hand</h2>
<p>The SSE spec is a simple text format, but parsing it correctly means handling several edge cases: events split across multiple data lines, comment lines starting with a colon, fields with no value, and incomplete lines at the end of a chunk.</p>
<p>Here's the core state machine inside <code>streamSSE()</code>:</p>
<pre><code class="language-typescript">let buffer = "";
let currentEvent: Partial&lt;SSEEvent&gt; = { data: "", event: null, id: null };
let hasData = false;

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split(/\r\n|\r|\n/);
  buffer = lines.pop() || ""; // keep the last incomplete line for the next chunk

  for (const line of lines) {
    if (line === "") {
      if (hasData) {
        const event: SSEEvent = {
          id: currentEvent.id ?? lastEventId,
          event: currentEvent.event ?? null,
          data: currentEvent.data!.endsWith("\n")
            ? currentEvent.data!.slice(0, -1)
            : currentEvent.data!,
          retry: currentEvent.retry,
        };
        if (event.id) lastEventId = event.id;
        yield event;
        currentEvent = { data: "", event: null, id: null };
        hasData = false;
      }
      continue;
    }

    if (line.startsWith(":")) continue; // comment line, ignore

    const colonIndex = line.indexOf(":");
    const field = colonIndex === -1 ? line : line.slice(0, colonIndex);
    let valueStr = colonIndex === -1 ? "" : line.slice(colonIndex + 1);
    if (valueStr.startsWith(" ")) valueStr = valueStr.slice(1);

    switch (field) {
      case "data":
        currentEvent.data += valueStr + "\n";
        hasData = true;
        break;
      case "event":
        currentEvent.event = valueStr;
        break;
      case "id":
        if (valueStr.indexOf("\0") === -1) currentEvent.id = valueStr;
        break;
      case "retry":
        const retry = parseInt(valueStr, 10);
        if (!isNaN(retry)) retryInterval = retry;
        break;
    }
  }
}
</code></pre>
<p>A network chunk doesn't respect line boundaries. A single <code>read()</code> call might end mid-line, so the last, possibly incomplete line is held back in <code>buffer</code> and prepended to the next chunk rather than processed early. This is the part of SSE parsing that's easy to get wrong if you reach for a naïve <code>response.text()</code> and a string split.</p>
<p>The blank line is what ends an event. SSE events don't have a fixed-length header. The spec says a blank line marks the boundary, so the parser only yields an event once it has seen one.</p>
<p>The <code>id</code> field is rejected outright if it contains a null byte, per the spec. That's a small detail that almost no hand-rolled implementation gets right on the first try.</p>
<h2 id="heading-how-to-implement-reconnection-with-last-event-id">How to Implement Reconnection with Last-Event-ID</h2>
<p>This is the part of SSE that gives it a real advantage over a plain <code>fetch</code> stream: built-in support for resuming after a disconnect without losing your place.</p>
<pre><code class="language-typescript">let lastEventId: string | null = null;

while (retryCount &lt;= retries) {
  const headers = { ...customHeaders };
  if (lastEventId) {
    (headers as any)["Last-Event-ID"] = lastEventId;
  }

  const response = await fetch(url, { method: "GET", headers, signal });
  // ... read and parse events, updating lastEventId as they arrive
}
</code></pre>
<p>Every time an event with an <code>id</code> field arrives, <code>lastEventId</code> is updated. If the connection drops and the client reconnects, it sends <code>Last-Event-ID</code> in the request headers. A well-behaved server can use that header to resume the stream from the right point instead of replaying everything or skipping ahead.</p>
<p>This only works if the server actually honors the header, so it's a contract between client and server, not something the client can guarantee alone. But having the client track and send it correctly is the necessary half of that contract.</p>
<h2 id="heading-how-to-handle-retries-with-backoff">How to Handle Retries with Backoff</h2>
<p>Both <code>stream()</code> and <code>streamSSE()</code> retry on failure, but they do it slightly differently based on what failed.</p>
<p><code>stream()</code> uses a simple linear backoff tied to the retry count:</p>
<pre><code class="language-typescript">await new Promise((resolve) =&gt; setTimeout(resolve, 1000 * retryCount));
</code></pre>
<p><code>streamSSE()</code> respects the server-specified <code>retry</code> field from the SSE spec when one is provided, falling back to a default otherwise:</p>
<pre><code class="language-typescript">let retryInterval = 1000;
// ... updated from the "retry" field if the server sends one
await new Promise((r) =&gt; setTimeout(r, retryInterval));
</code></pre>
<p>Letting the server influence the retry interval matters in practice. A server under load can tell clients to back off longer, which is exactly the kind of cooperative behavior the SSE spec was designed to support.</p>
<p>In both functions, an aborted <code>AbortSignal</code> always short-circuits the retry loop. Treating a deliberate cancellation as a retryable failure is a common bug, and the fix is just checking <code>signal?.aborted</code> before deciding to retry.</p>
<h2 id="heading-how-to-use-this-with-react">How to Use This with React</h2>
<p>Because both functions are async generators, integrating with React state is a matter of looping and calling <code>setState</code> per chunk:</p>
<pre><code class="language-typescript">function ChatComponent() {
  const [messages, setMessages] = useState("");

  useEffect(() =&gt; {
    const controller = new AbortController();

    (async () =&gt; {
      try {
        for await (const chunk of stream("/api/chat", { signal: controller.signal })) {
          setMessages((prev) =&gt; prev + chunk);
        }
      } catch (err: any) {
        if (err.name !== "AbortError") console.error(err);
      }
    })();

    return () =&gt; controller.abort();
  }, []);

  return &lt;div&gt;{messages}&lt;/div&gt;;
}
</code></pre>
<p>The cleanup function calling <code>controller.abort()</code> is doing real work here. Without it, navigating away from the component while a stream is still active leaves the fetch running in the background, updating state on an unmounted component.</p>
<h2 id="heading-how-to-use-this-with-react-server-components">How to Use This with React Server Components</h2>
<p>Because the generator yields values one at a time, you can also drive a recursive Suspense boundary directly from the async iterator, streaming HTML to the client as each chunk arrives:</p>
<pre><code class="language-typescript">async function StreamViewer({ iterator }: { iterator: AsyncIterator&lt;string&gt; }) {
  const { value, done } = await iterator.next();
  if (done) return null;

  return (
    &lt;span&gt;
      {value}
      &lt;Suspense&gt;
        &lt;StreamViewer iterator={iterator} /&gt;
      &lt;/Suspense&gt;
    &lt;/span&gt;
  );
}

export default function Page() {
  const dataStream = stream("https://api.example.com/stream");
  const iterator = dataStream[Symbol.asyncIterator]();

  return (
    &lt;Suspense fallback="Loading..."&gt;
      &lt;StreamViewer iterator={iterator} /&gt;
    &lt;/Suspense&gt;
  );
}
</code></pre>
<p>Each recursive call awaits the next chunk and renders a nested <code>Suspense</code> boundary for the rest. React streams each piece of HTML to the client as it resolves, rather than waiting for the entire response.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>A reliable streaming client needs to handle more than the success path. Connections drop, chunks arrive split across line boundaries, and cancellation needs to be distinguished from failure.</p>
<p>Ore's approach to this is built from a small set of ideas:</p>
<ul>
<li><p>Expose streams as async generators so consumers can use <code>for await...of</code></p>
</li>
<li><p>Parse SSE by hand, field by field, respecting the spec's blank-line event boundaries and buffering incomplete lines across chunks</p>
</li>
<li><p>Track <code>Last-Event-ID</code> so reconnection can resume rather than restart</p>
</li>
<li><p>Treat retries and cancellation as separate concerns</p>
</li>
<li><p>Stay framework-agnostic at the core, with thin integration points for React and React Server Components</p>
</li>
</ul>
<p>That combination is what separates a streaming client that works in a demo from one that holds up against real network conditions. You can explore the full source code at <a href="https://github.com/glamboyosa/ore">github.com/glamboyosa/ore</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Declarative Partial Updates Work in HTML ]]>
                </title>
                <description>
                    <![CDATA[ HTML has always supported streaming. The server doesn't need to build an entire page in memory before sending it to the browser. It can send the initial HTML first, then send more chunks as each chunk ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-declarative-partial-updates-work-in-html/</link>
                <guid isPermaLink="false">6a19f51f7cae2251376e62cb</guid>
                
                    <category>
                        <![CDATA[ HTML5 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ streaming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sumit Saha ]]>
                </dc:creator>
                <pubDate>Fri, 29 May 2026 20:20:47 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d63aaead-37ef-4218-a3db-1f507d638317.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>HTML has always supported streaming. The server doesn't need to build an entire page in memory before sending it to the browser. It can send the initial HTML first, then send more chunks as each chunk is ready. The browser parses those chunks and displays the page in order. This is one reason why HTML seems fast.</p>
<p>But traditional HTML streaming has a strict rule. The HTML comes in the order of the document. If the browser gets the header first, then the sidebar, and finally the main content, it parses those chunks in that order. If a slow database query blocks a chunk of the page early on, the next chunk often has to wait until it's ready on the server.</p>
<p>JavaScript frameworks have been solving this problem for years. Server-rendering frameworks handle shell, suspense boundaries, loading state, and late content streaming. Some frameworks use inline script to patch the existing DOM. Libraries like HTMX allow developers to update parts of a page with server-generated HTML.</p>
<p>But these solutions require JavaScript somewhere. Declarative Partial Updates raise a different question. What if HTML had its own way of saying,</p>
<blockquote>
<p>When this content comes in, put it there?</p>
</blockquote>
<p>That's the idea behind Chrome's declarative partial updates proposal.</p>
<p>In this article, you'll learn what problems declarative partial updates aim to solve, how the proposed placeholder syntax works, how out-of-order HTML streaming differs from normal streaming, how the related JavaScript HTML insertion APIs fit in, and why it should be considered an experimental feature of the browser rather than production-ready HTML.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-problem-declarative-partial-updates-try-to-solve">What Problem Declarative Partial Updates Try to Solve</a></p>
</li>
<li><p><a href="#heading-how-traditional-html-streaming-works">How Traditional HTML Streaming Works</a></p>
</li>
<li><p><a href="#heading-why-frameworks-already-work-around-this-problem">Why Frameworks Already Work Around This Problem</a></p>
</li>
<li><p><a href="#heading-what-declarative-partial-updates-add-to-html">What Declarative Partial Updates Add to HTML</a></p>
</li>
<li><p><a href="#heading-how-marker-placeholders-work">How Marker Placeholders Work</a></p>
</li>
<li><p><a href="#heading-how-start-and-end-range-placeholders-work">How Start and End Range Placeholders Work</a></p>
</li>
<li><p><a href="#heading-how-multiple-updates-work">How Multiple Updates Work</a></p>
</li>
<li><p><a href="#heading-how-interleaved-updates-work">How Interleaved Updates Work</a></p>
</li>
<li><p><a href="#heading-how-this-compares-to-react-htmx-astro-and-php">How This Compares to React, HTMX, Astro, and PHP</a></p>
</li>
<li><p><a href="#heading-how-the-javascript-html-insertion-apis-fit-in">How the JavaScript HTML Insertion APIs Fit In</a></p>
</li>
<li><p><a href="#heading-how-to-build-a-small-nodejs-streaming-demo">How to Build a Small Node.js Streaming Demo</a></p>
</li>
<li><p><a href="#heading-browser-support-and-current-status">Browser Support and Current Status</a></p>
</li>
<li><p><a href="#heading-security-sanitization-and-sharp-edges">Security, Sanitization, and Sharp Edges</a></p>
</li>
<li><p><a href="#heading-what-this-means-for-web-developers">What This Means for Web Developers</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-problem-declarative-partial-updates-try-to-solve">What Problem Declarative Partial Updates Try to Solve</h2>
<p>Consider a product page. The server already knows the page title, navigation, footer, and product details. But the recommendations section requires a slow database query. With traditional server-rendered HTML, you have two common options:</p>
<ul>
<li><p>First, the server waits until everything is ready, then sends a full HTML response. This keeps the code simple, but the user waits a long time before seeing anything useful.</p>
</li>
<li><p>Second, the server streams the HTML in stages. It sends the top of the page first, then sends the rest as it's ready. This seems to improve performance, because the browser starts rendering before the full response is finished.</p>
</li>
</ul>
<p>But streaming alone doesn't completely solve this problem. The browser still parses the HTML sequentially. If there's a slow recommendation block at the beginning of the document, the content after that block will wait behind it, unless you restructure the document, add JavaScript, or use a framework abstraction.</p>
<p>WICG Patching Explainer describes two limitations of traditional HTML streaming:</p>
<ol>
<li><p>HTML content is streamed in DOM order.</p>
</li>
<li><p>After the initial document parsing step, streaming is no longer as active as before.</p>
</li>
</ol>
<p>Declarative partial update attempts to relax the first limitation. It allows the server to first send a placeholder and then send the actual content in the response. The browser applies that next content over the previous placeholder. This patch doesn't require any custom client-side DOM patching code.</p>
<img src="https://cdn.hashnode.com/uploads/covers/684c97407a181815db5e3102/69771a7d-a657-47ee-be94-9a2d771bcbe8.png" alt="Diagram showing traditional HTML streaming." style="display:block;margin:0 auto" width="1200" height="675" loading="lazy">

<p>In the above diagram, the server sends HTML chunks in sequence. The browser can render early chunks before the response ends, but the rendered order still follows the response order.</p>
<h2 id="heading-how-traditional-html-streaming-works">How Traditional HTML Streaming Works</h2>
<p>Before studying the proposal, you need to understand its basic structure. A server sends an HTTP response body. That body contains HTML. The browser reads the response as soon as it arrives. It doesn't need to wait for the entire response body to parse the first tags.</p>
<p>Here's a tiny Node.js example:</p>
<pre><code class="language-javascript">import http from "node:http";

const sleep = (ms) =&gt; new Promise((resolve) =&gt; setTimeout(resolve, ms));

const server = http.createServer(async (req, res) =&gt; {
    res.writeHead(200, {
        "Content-Type": "text/html; charset=utf-8",
    });

    res.write(`
    &lt;!doctype html&gt;
    &lt;html&gt;
      &lt;head&gt;
        &lt;title&gt;Normal HTML Streaming&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;h1&gt;Normal HTML Streaming&lt;/h1&gt;
        &lt;p&gt;This part arrives first.&lt;/p&gt;
  `);

    await sleep(2000);

    res.write(`
        &lt;p&gt;This part arrives after two seconds.&lt;/p&gt;
  `);

    await sleep(2000);

    res.end(`
        &lt;p&gt;This part arrives after four seconds.&lt;/p&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  `);
});

server.listen(3000, () =&gt; {
    console.log("Server running at http://localhost:3000");
});
</code></pre>
<p>This example creates a small HTTP server using Node's built-in <code>http</code> module. When you visit the page, the server sends a response in three separate HTML chunks.</p>
<p>The first <code>res.write()</code> immediately sends the document shell, title, and first paragraph. Then <code>sleep(2000)</code> pauses the server for two seconds before the next <code>res.write()</code> sends another paragraph. After another pause, <code>res.end()</code> sends the last paragraph and closes the HTML document.</p>
<p>The browser starts rendering the first chunk before the entire response is complete, then adds subsequent paragraphs as more HTML arrives.</p>
<p>This demonstrates that simple HTML streaming works, but the content is displayed in the same order that the server sends it.</p>
<p>Now open this page in a browser.</p>
<pre><code class="language-plaintext">http://localhost:3000
</code></pre>
<p>You'll see the first part of the page before the entire response is complete. As the server sends more chunks, the browser continues to load.</p>
<p>This behavior is old and functional. But notice its structure. The server writes the first paragraph, then the second paragraph, and then the third paragraph. The browser receives them in the same order. It also places them in the DOM in the same order.</p>
<p>Traditional streaming lets you send previous content first. But it doesn't give you a native way to say that this next chunk will be inside a previous placeholder. Declarative partial updates target that missing part.</p>
<h2 id="heading-why-frameworks-already-work-around-this-problem">Why Frameworks Already Work Around This Problem</h2>
<p>Modern frameworks already create experiences where parts of a page are rendered as they're ready. React server components and suspense-based server rendering are common examples. A framework can first send a shell, show a fallback, and then stream the full content later.</p>
<p>But the browser doesn't understand a React boundary as native HTML. The framework has to encode its own protocol. As the WICG patching explainer notes, React uses inline <code>&lt;script&gt;</code> tags to stream content out of order and modify the already parsed DOM.</p>
<p>This works because JavaScript has full DOM access. But it also means that the browser isn't applying the update as normal HTML. A framework runtime or framework-generated script participates in this patching.</p>
<p>HTMX solves a related class of problems in another way. It allows you to request server-rendered HTML and swap parts of the page. This model is practical and popular, but it still relies on a JavaScript library.</p>
<p>Astro popularized the islands architecture, where independently interactive parts of a page are essentially contained within a static HTML document. Chrome's article on Declarative Partial Updates mentions the island architecture as a use case for this proposal.</p>
<p>Declarative Partial Updates doesn't replace those tools. It proposes a low-level browser primitive. Frameworks can later adopt this primitive. Server-rendered apps can also use it directly in simpler cases.</p>
<p>The key change is this: instead of sending JavaScript that finds a DOM node and modifies it, the server sends HTML that declares where the node is located.</p>
<h2 id="heading-what-declarative-partial-updates-add-to-html">What Declarative Partial Updates Add to HTML</h2>
<p>Chrome's proposal has two main parts:</p>
<ul>
<li><p>The first part is HTML placeholders and out-of-order streaming using the <code>&lt;template&gt;</code> patch.</p>
</li>
<li><p>The second part is a new set of JavaScript methods for inserting and streaming HTML into existing documents.</p>
</li>
</ul>
<p>Chrome's announcement states that these APIs are ready for developers to test starting in Chrome 148 using the Experimental Web Platform Features flag. This status is important. It's not currently a stable cross-browser HTML. It's an active proposal and implementation test.</p>
<p>For the declarative streaming part, the proposal uses two HTML concepts:</p>
<ol>
<li><p>Processing instruction placeholders.</p>
</li>
<li><p>The <code>&lt;template&gt;</code> element with the <code>for</code> attribute.</p>
</li>
</ol>
<p>A simplified example looks like this:</p>
<pre><code class="language-html">&lt;div&gt;&lt;?marker name="profile-card"&gt;&lt;/div&gt;

&lt;template for="profile-card"&gt;
    &lt;article&gt;
        &lt;h2&gt;Ada Lovelace&lt;/h2&gt;
        &lt;p&gt;Mathematician and early computing pioneer.&lt;/p&gt;
    &lt;/article&gt;
&lt;/template&gt;
</code></pre>
<p>The placeholder marks a location. The template contains the content. When the browser parses the template, it finds the matching placeholder and applies the template content there. After parsing, the final DOM behaves like this:</p>
<pre><code class="language-html">&lt;div&gt;
    &lt;article&gt;
        &lt;h2&gt;Ada Lovelace&lt;/h2&gt;
        &lt;p&gt;Mathematician and early computing pioneer.&lt;/p&gt;
    &lt;/article&gt;
&lt;/div&gt;
</code></pre>
<p>The server sent the placeholder first and the actual content later. The browser combined them declaratively. That's the basic idea. The HTML gets a native patching instruction without any custom scripts.</p>
<img src="https://cdn.hashnode.com/uploads/covers/684c97407a181815db5e3102/3dbabe3a-5dc3-4627-8641-138a64af2291.png" alt="Diagram showing Declarative Partial Updates" style="display:block;margin:0 auto" width="1200" height="675" loading="lazy">

<p>In the diagram above, the browser receives and parses the placeholders first. Subsequent templates target those placeholders, so the rendered page updates specific areas without appending all the late HTML at the bottom.</p>
<h2 id="heading-how-marker-placeholders-work">How Marker Placeholders Work</h2>
<p>The simplest placeholder is <code>&lt;?marker&gt;</code>. A marker refers to a place in a document where HTML code will appear later. Here's a small example from the proposed model:</p>
<pre><code class="language-html">&lt;section&gt;
    &lt;h2&gt;Team&lt;/h2&gt;

    &lt;ul&gt;
        &lt;?marker name="team-members"&gt;
    &lt;/ul&gt;
&lt;/section&gt;

&lt;template for="team-members"&gt;
    &lt;li&gt;Ada Lovelace&lt;/li&gt;
&lt;/template&gt;
</code></pre>
<p>The <code>name</code> attribute of the marker connects it to the <code>for</code> attribute of the template. When the browser parses the template, it finds the marker named <code>team-members</code> and inserts the template content at the location of that marker.</p>
<p>The final DOM behaves like this:</p>
<pre><code class="language-html">&lt;section&gt;
    &lt;h2&gt;Team&lt;/h2&gt;

    &lt;ul&gt;
        &lt;li&gt;Ada Lovelace&lt;/li&gt;
    &lt;/ul&gt;
&lt;/section&gt;
</code></pre>
<p>While this may seem simple, timing is important. The placeholder can come at the beginning of the response. The template can come later. This means that the browser can render a working shell first, then patch that specific location when the server sends the delayed content.</p>
<p>This is different from normal streaming, where the next HTML document is displayed later in the order. A marker specifies a target for the next HTML.</p>
<h3 id="heading-why-processing-instructions-matter">Why Processing Instructions Matter</h3>
<p>The placeholder syntax may look unusual if you mostly write HTML.</p>
<pre><code class="language-html">&lt;?marker name="profile"&gt;
</code></pre>
<p>This is similar to the XML Processing Instruction syntax. MDN explains that processing instructions exist in the DOM, but are currently treated as comments in HTML documents. The Declarative Partial Updates proposal gives browser-level meaning to selected processing instructions for HTML patching. This is why this feature requires browser support.</p>
<p>In current stable HTML, writing <code>&lt;?marker name="profile"&gt;</code> doesn't create a patch point in all browsers. Without support, it treats content as ignored markup or comments. So you should consider this syntax as a recommended behavior, not as established HTML behavior.</p>
<h2 id="heading-how-start-and-end-range-placeholders-work">How Start and End Range Placeholders Work</h2>
<p>A single marker gives you an insertion point. But many UI updates need to replace a whole region.</p>
<p>For example, a page might show a loading message first:</p>
<pre><code class="language-html">&lt;section&gt;
    &lt;h2&gt;Recommendations&lt;/h2&gt;

    &lt;?start name="recommendations"&gt;
    &lt;p&gt;Loading recommendations...&lt;/p&gt;
    &lt;?end&gt;
&lt;/section&gt;
</code></pre>
<p>Later, the server sends the real content:</p>
<pre><code class="language-html">&lt;template for="recommendations"&gt;
    &lt;ul&gt;
        &lt;li&gt;Advanced CSS Layouts&lt;/li&gt;
        &lt;li&gt;Modern HTML APIs&lt;/li&gt;
        &lt;li&gt;Web Performance Basics&lt;/li&gt;
    &lt;/ul&gt;
&lt;/template&gt;
</code></pre>
<p>The browser replaces the range between <code>&lt;?start&gt;</code> and <code>&lt;?end&gt;</code> with the template content.</p>
<p>The final DOM behaves like this:</p>
<pre><code class="language-html">&lt;section&gt;
    &lt;h2&gt;Recommendations&lt;/h2&gt;

    &lt;ul&gt;
        &lt;li&gt;Advanced CSS Layouts&lt;/li&gt;
        &lt;li&gt;Modern HTML APIs&lt;/li&gt;
        &lt;li&gt;Web Performance Basics&lt;/li&gt;
    &lt;/ul&gt;
&lt;/section&gt;
</code></pre>
<p>This is near a loading boundary. The document starts with the necessary fallback content. The server finishes the slow work later. When the appropriate template arrives, the browser replaces the fallback.</p>
<p>The <code>&lt;?end&gt;</code> processing directive in the explainer is optional, but it makes the examples easier to understand. Use it when teaching or testing the feature.</p>
<h3 id="heading-marker-versus-range">Marker Versus Range</h3>
<p>Use markers to add content at a specific location. Use a start and end range to replace temporary content.</p>
<p>That means, add something here:</p>
<pre><code class="language-html">&lt;ul&gt;
    &lt;?marker name="new-item"&gt;
&lt;/ul&gt;
</code></pre>
<p>This says, replace this whole loading region later:</p>
<pre><code class="language-html">&lt;div&gt;
    &lt;?start name="profile"&gt;
    &lt;p&gt;Loading profile...&lt;/p&gt;
    &lt;?end&gt;
&lt;/div&gt;
</code></pre>
<p>The second method is more suitable for real interfaces, as users see meaningful fallback content while they wait.</p>
<h2 id="heading-how-multiple-updates-work">How Multiple Updates Work</h2>
<p>A placeholder doesn't have to be just an update. A server can send a patch, leave another marker, and then send another patch later. This is important for lists, feeds, notifications, logs, comments, and search results.</p>
<p>Here's a simplified example:</p>
<pre><code class="language-html">&lt;ul&gt;
    &lt;?marker name="messages"&gt;
&lt;/ul&gt;

&lt;template for="messages"&gt;
    &lt;li&gt;First message&lt;/li&gt;
    &lt;?marker name="messages"&gt;
&lt;/template&gt;

&lt;template for="messages"&gt;
    &lt;li&gt;Second message&lt;/li&gt;
    &lt;?marker name="messages"&gt;
&lt;/template&gt;

&lt;template for="messages"&gt;
    &lt;li&gt;Third message&lt;/li&gt;
&lt;/template&gt;
</code></pre>
<p>The final DOM behaves like this:</p>
<pre><code class="language-html">&lt;ul&gt;
    &lt;li&gt;First message&lt;/li&gt;
    &lt;li&gt;Second message&lt;/li&gt;
    &lt;li&gt;Third message&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Each patch adds content and creates the next outlet. This model is suitable for streaming data, where the server finds the results over time.</p>
<p>Think of a search page. The server finds the first result quickly. The second result requires another service call. The third result requires a database lookup.</p>
<p>Traditional HTML streaming places each result where it appears in the response. Declarative patching allows each result to target a known outlet in the document. This doesn't mean that every list should use this feature. It means that HTML will have a native primitive for this pattern.</p>
<h2 id="heading-how-interleaved-updates-work">How Interleaved Updates Work</h2>
<p>The most interesting part is interleaving.</p>
<p>Suppose a page has three regions:</p>
<ol>
<li><p>A profile card.</p>
</li>
<li><p>A recommendations panel.</p>
</li>
<li><p>A notification list.</p>
</li>
</ol>
<p>Each region requires different server management. The profile card is ready after one second. Notifications are ready after two seconds. Recommendations are ready after four seconds. In simple streaming, the order of your documents determines what the user sees first.</p>
<p>With declarative patching, the server sends placeholders early:</p>
<pre><code class="language-html">&lt;main&gt;
    &lt;section&gt;
        &lt;h2&gt;Profile&lt;/h2&gt;
        &lt;?start name="profile"&gt;
        &lt;p&gt;Loading profile...&lt;/p&gt;
        &lt;?end&gt;
    &lt;/section&gt;

    &lt;section&gt;
        &lt;h2&gt;Recommendations&lt;/h2&gt;
        &lt;?start name="recommendations"&gt;
        &lt;p&gt;Loading recommendations...&lt;/p&gt;
        &lt;?end&gt;
    &lt;/section&gt;

    &lt;section&gt;
        &lt;h2&gt;Notifications&lt;/h2&gt;
        &lt;?start name="notifications"&gt;
        &lt;p&gt;Loading notifications...&lt;/p&gt;
        &lt;?end&gt;
    &lt;/section&gt;
&lt;/main&gt;
</code></pre>
<p>Then the server sends templates in readiness order, not visual order:</p>
<pre><code class="language-html">&lt;template for="profile"&gt;
    &lt;p&gt;Ada Lovelace&lt;/p&gt;
&lt;/template&gt;

&lt;template for="notifications"&gt;
    &lt;ul&gt;
        &lt;li&gt;You have one new message.&lt;/li&gt;
    &lt;/ul&gt;
&lt;/template&gt;

&lt;template for="recommendations"&gt;
    &lt;ul&gt;
        &lt;li&gt;Modern HTML APIs&lt;/li&gt;
        &lt;li&gt;Streaming Web Apps&lt;/li&gt;
    &lt;/ul&gt;
&lt;/template&gt;
</code></pre>
<p>The browser patches each target region. The user gets the profile first, the notification second, and the recommendations third, although in the main document structure the recommendations appear before the notifications.</p>
<p>This is why 'out-of-order streaming' is the key. The HTML response comes in as a stream. But the rendered update doesn't have to follow the same visual order as the response chunks.</p>
<p>The WICG Explainer describes interleaved patching across different outlets. This is one of the strongest reasons this proposal matters. It gives browser-native HTML a behavior developers usually associate with framework-level streaming.</p>
<h2 id="heading-how-this-compares-to-react-htmx-astro-and-php">How This Compares to React, HTMX, Astro, and PHP</h2>
<p>This feature will allow for comparisons. These comparisons can help you understand the main idea, but can also be confusing if you go too deep.</p>
<h3 id="heading-react">React</h3>
<p>React server rendering already supports streaming UI and fallback state. But the rendering model is React's own. When React needs to patch late-added content to an already parsed document, it uses framework-generated directives and JavaScript.</p>
<p>Declarative Partial Updates bring a low-level part of this concept to HTML. It doesn't replace React's component model, state model, event handling, reconciliation, or ecosystem.</p>
<h3 id="heading-htmx">HTMX</h3>
<p>HTMX allows you to request HTML from the server and replace it on the page. It's conceptually close, because it treats HTML as the main response format. But HTMX is a JavaScript library.</p>
<p>The goal of declarative partial updates is to make some HTML patching behavior a feature of the browser itself. HTMX still handles many interaction patterns beyond this proposal.</p>
<h3 id="heading-astro">Astro</h3>
<p>Astro popularized island-based rendering. A page can consist of mostly static HTML and isolated interactive regions. Chrome cites island architecture as a use case, because independent regions of a page are often rendered at different times.</p>
<p>Declarative partial updates can help servers serve island HTML as each region is rendered.</p>
<h3 id="heading-php">PHP</h3>
<p>Its syntax may remind you of PHP, as server-side code and HTML have been used together for decades. But this proposal is not PHP inside the browser.</p>
<p>PHP runs on the server. Declarative partial updates are the browser's parsing behavior for streamed HTML. A useful workflow comparison can be made with it.</p>
<p>It makes it easier to stream server-generated HTML to specific locations on the page without having to send a separate JavaScript patch for each update.</p>
<h2 id="heading-how-the-javascript-html-insertion-apis-fit-in">How the JavaScript HTML Insertion APIs Fit In</h2>
<p>Declarative placeholders solve part of the problem. They help the browser patch the streamed HTML to a previous location in the same document.</p>
<p>But not every update comes as part of the original HTML response. Many apps fetch HTML after the page has loaded.</p>
<p>For example, a page might fetch:</p>
<ul>
<li><p>A comments section</p>
</li>
<li><p>A cart summary</p>
</li>
<li><p>A profile dropdown</p>
</li>
<li><p>A notification panel</p>
</li>
<li><p>A search result preview</p>
</li>
</ul>
<p>Today, JavaScript has several ways to insert HTML into a document:</p>
<pre><code class="language-js">element.innerHTML = "&lt;p&gt;Hello&lt;/p&gt;";
element.outerHTML = "&lt;section&gt;Hello&lt;/section&gt;";
element.insertAdjacentHTML("beforeend", "&lt;p&gt;Hello&lt;/p&gt;");
</code></pre>
<p>These APIs work, but they don't behave the same way.</p>
<ul>
<li><p>Some replace content.</p>
</li>
<li><p>Some insert beside content.</p>
</li>
<li><p>Some parse in a specific context.</p>
</li>
<li><p>Some interact with sanitization and security rules differently.</p>
</li>
</ul>
<p>Chrome's declarative partial updates work also includes a revamped set of HTML insertion and streaming APIs, aiming to create a more explicit naming convention for common insertion patterns.</p>
<p>Here's the basic idea:</p>
<table>
<thead>
<tr>
<th>Action</th>
<th>Static method</th>
<th>Streaming method</th>
</tr>
</thead>
<tbody><tr>
<td>Replace an element's children</td>
<td><code>setHTML()</code></td>
<td><code>streamHTML()</code></td>
</tr>
<tr>
<td>Replace the element itself</td>
<td><code>replaceWithHTML()</code></td>
<td><code>streamReplaceWithHTML()</code></td>
</tr>
<tr>
<td>Insert before the element</td>
<td><code>beforeHTML()</code></td>
<td><code>streamBeforeHTML()</code></td>
</tr>
<tr>
<td>Insert as first child</td>
<td><code>prependHTML()</code></td>
<td><code>streamPrependHTML()</code></td>
</tr>
<tr>
<td>Insert as last child</td>
<td><code>appendHTML()</code></td>
<td><code>streamAppendHTML()</code></td>
</tr>
<tr>
<td>Insert after the element</td>
<td><code>afterHTML()</code></td>
<td><code>streamAfterHTML()</code></td>
</tr>
</tbody></table>
<p>The names tell you where the HTML goes. That's the main improvement.</p>
<p>Instead of remembering how <code>innerHTML</code>, <code>outerHTML</code>, <code>insertAdjacentHTML</code>, and <code>createContextualFragment()</code> differ, the method name describes the action.</p>
<h3 id="heading-static-insertion">Static Insertion</h3>
<p>A static method accepts a complete HTML string.</p>
<pre><code class="language-js">const card = document.querySelector("#profile-card");

card.setHTML(`
  &lt;article&gt;
    &lt;h2&gt;Ada Lovelace&lt;/h2&gt;
    &lt;p&gt;Mathematician and early computing pioneer.&lt;/p&gt;
  &lt;/article&gt;
`);
</code></pre>
<p><code>setHTML()</code> parses the string, sanitizes it, and inserts the result into the element.</p>
<p>MDN describes <code>setHTML()</code> as an XSS-safe way to parse and sanitize an HTML string before including it in the DOM. This is safer than assigning untrusted HTML to <code>innerHTML</code>.</p>
<h3 id="heading-streaming-insertion">Streaming Insertion</h3>
<p>The streaming method doesn't require the entire HTML string to begin with. It creates a writable stream. Then JavaScript writes fragments to that stream.</p>
<p>A simplified example looks like this:</p>
<pre><code class="language-js">const output = document.querySelector("#output");
const writer = output.streamHTMLUnsafe().getWriter();

await writer.write("&lt;p&gt;First streamed chunk&lt;/p&gt;");
await writer.write("&lt;p&gt;Second streamed chunk&lt;/p&gt;");
await writer.close();
</code></pre>
<p>This model is important because streaming is one of the strengths of HTML. The browser doesn't always have to wait for a complete response before inserting meaningful markup.</p>
<p>Chrome also shows this pattern with <code>fetch()</code>:</p>
<pre><code class="language-js">const output = document.querySelector("#output");
const response = await fetch("/comments");

response.body
    .pipeThrough(new TextDecoderStream())
    .pipeTo(output.streamHTMLUnsafe());
</code></pre>
<p>Here, the response body streams from the network. <code>TextDecoderStream</code> converts bytes into text. The result pipes into the element's HTML stream.</p>
<h3 id="heading-why-the-unsafe-name-matters">Why the Unsafe Name Matters</h3>
<p>Some methods have <code>Unsafe</code> versions.</p>
<p>For example:</p>
<pre><code class="language-js">setHTMLUnsafe();
streamHTMLUnsafe();
appendHTMLUnsafe();
streamAppendHTMLUnsafe();
</code></pre>
<p>The name is intentional. MDN warns that <code>setHTMLUnsafe()</code> parses the input as HTML and writes the result to the DOM. If you don't pass a sanitizer, no sanitizer is used.</p>
<p>Use the safe versions for untrusted content. Consider the unsafe versions as a low-level tool, especially in cases where you have full control over the HTML source or pass an appropriate sanitizer.</p>
<p>For the demo in this article, keep the user input away from the HTML string. The goal is to teach streaming behavior, not unsafe HTML insertion.</p>
<img src="https://cdn.hashnode.com/uploads/covers/684c97407a181815db5e3102/b4a23dc7-6eb5-4eab-b481-09849f3eb97c.png" alt="Diagram showing two partial update models" style="display:block;margin:0 auto" width="1200" height="675" loading="lazy">

<p>In the diagram above, declarative patching begins within the streamed document. JavaScript streaming insertion begins after the script code selects an element and pipes a streamed response into it.</p>
<h2 id="heading-how-to-build-a-small-nodejs-streaming-demo">How to Build a Small Node.js Streaming Demo</h2>
<p>Now let's build a small demo.</p>
<p>You will create one Node.js server with three routes:</p>
<table>
<thead>
<tr>
<th>Route</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td><code>/normal-stream</code></td>
<td>Shows regular in-order HTML streaming</td>
</tr>
<tr>
<td><code>/partial-updates-demo</code></td>
<td>Shows proposed declarative partial update syntax</td>
</tr>
<tr>
<td><code>/stream-html-api-demo</code></td>
<td>Shows JavaScript streaming insertion syntax</td>
</tr>
</tbody></table>
<p>The demo uses Node's built-in <code>http</code> module.</p>
<ul>
<li><p>No Express.</p>
</li>
<li><p>No frontend framework.</p>
</li>
<li><p>No build step.</p>
</li>
</ul>
<p>That keeps the focus on HTML streaming.</p>
<h3 id="heading-create-the-project">Create the Project</h3>
<p>Create a new folder:</p>
<pre><code class="language-bash">mkdir html-partial-updates-demo
cd html-partial-updates-demo
</code></pre>
<p>Create a <code>package.json</code> file:</p>
<pre><code class="language-json">{
    "name": "html-partial-updates-demo",
    "version": "1.0.0",
    "type": "module",
    "scripts": {
        "dev": "node server.js"
    }
}
</code></pre>
<p>Create a <code>server.js</code> file:</p>
<pre><code class="language-js">import http from "node:http";

const sleep = (ms) =&gt; new Promise((resolve) =&gt; setTimeout(resolve, ms));

const server = http.createServer(async (req, res) =&gt; {
    if (req.url === "/normal-stream") {
        return normalStream(req, res);
    }

    if (req.url === "/partial-updates-demo") {
        return partialUpdatesDemo(req, res);
    }

    if (req.url === "/stream-html-api-demo" || req.url === "/comments-stream") {
        return streamHtmlApiDemo(req, res);
    }

    res.writeHead(200, {
        "Content-Type": "text/html; charset=utf-8",
    });

    res.end(`
    &lt;!doctype html&gt;
    &lt;html&gt;
      &lt;head&gt;
        &lt;title&gt;HTML Partial Updates Demo&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;h1&gt;HTML Partial Updates Demo&lt;/h1&gt;

        &lt;ul&gt;
          &lt;li&gt;&lt;a href="/normal-stream"&gt;Normal HTML streaming&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="/partial-updates-demo"&gt;Declarative partial updates demo&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="/stream-html-api-demo"&gt;Streaming HTML API demo&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  `);
});

async function normalStream(req, res) {
    res.writeHead(200, {
        "Content-Type": "text/html; charset=utf-8",
    });

    res.write(`
    &lt;!doctype html&gt;
    &lt;html&gt;
      &lt;head&gt;
        &lt;title&gt;Normal HTML Streaming&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;h1&gt;Normal HTML Streaming&lt;/h1&gt;

        &lt;p&gt;This paragraph arrives immediately.&lt;/p&gt;
  `);

    await sleep(1500);

    res.write(`
        &lt;p&gt;This paragraph arrives after 1.5 seconds.&lt;/p&gt;
  `);

    await sleep(1500);

    res.end(`
        &lt;p&gt;This paragraph arrives after 3 seconds.&lt;/p&gt;

        &lt;p&gt;
          &lt;a href="/"&gt;Back to home&lt;/a&gt;
        &lt;/p&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  `);
}

async function partialUpdatesDemo(req, res) {
    res.writeHead(200, {
        "Content-Type": "text/html; charset=utf-8",
    });

    res.write(`
    &lt;!doctype html&gt;
    &lt;html&gt;
      &lt;head&gt;
        &lt;title&gt;Declarative Partial Updates Demo&lt;/title&gt;
        &lt;style&gt;
          body {
            font-family: system-ui, sans-serif;
            max-width: 760px;
            margin: 40px auto;
            line-height: 1.5;
          }

          section {
            border: 1px solid #ddd;
            border-radius: 12px;
            padding: 16px;
            margin-bottom: 16px;
          }

          .loading {
            color: #666;
          }
        &lt;/style&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;h1&gt;Declarative Partial Updates Demo&lt;/h1&gt;

        &lt;p&gt;
          This page sends placeholders first. Then the server sends
          matching templates when each piece of content is ready.
        &lt;/p&gt;

        &lt;section&gt;
          &lt;h2&gt;Profile&lt;/h2&gt;
          &lt;?start name="profile"&gt;
            &lt;p class="loading"&gt;Loading profile...&lt;/p&gt;
          &lt;?end&gt;
        &lt;/section&gt;

        &lt;section&gt;
          &lt;h2&gt;Recommendations&lt;/h2&gt;
          &lt;?start name="recommendations"&gt;
            &lt;p class="loading"&gt;Loading recommendations...&lt;/p&gt;
          &lt;?end&gt;
        &lt;/section&gt;

        &lt;section&gt;
          &lt;h2&gt;Notifications&lt;/h2&gt;
          &lt;?start name="notifications"&gt;
            &lt;p class="loading"&gt;Loading notifications...&lt;/p&gt;
          &lt;?end&gt;
        &lt;/section&gt;
  `);

    await sleep(1000);

    res.write(`
        &lt;template for="profile"&gt;
          &lt;p&gt;&lt;strong&gt;Ada Lovelace&lt;/strong&gt;&lt;/p&gt;
          &lt;p&gt;Mathematician and early computing pioneer.&lt;/p&gt;
        &lt;/template&gt;
  `);

    await sleep(1000);

    res.write(`
        &lt;template for="notifications"&gt;
          &lt;ul&gt;
            &lt;li&gt;You have one new message.&lt;/li&gt;
            &lt;li&gt;Your weekly report is ready.&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/template&gt;
  `);

    await sleep(2000);

    res.end(`
        &lt;template for="recommendations"&gt;
          &lt;ul&gt;
            &lt;li&gt;Modern HTML APIs&lt;/li&gt;
            &lt;li&gt;Streaming Web Apps&lt;/li&gt;
            &lt;li&gt;Web Performance Basics&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/template&gt;

        &lt;p&gt;
          &lt;a href="/"&gt;Back to home&lt;/a&gt;
        &lt;/p&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  `);
}

async function streamHtmlApiDemo(req, res) {
    if (req.url === "/comments-stream") {
        res.writeHead(200, {
            "Content-Type": "text/html; charset=utf-8",
        });

        res.write(`&lt;article&gt;&lt;p&gt;First streamed comment.&lt;/p&gt;&lt;/article&gt;`);

        await sleep(1000);

        res.write(`&lt;article&gt;&lt;p&gt;Second streamed comment.&lt;/p&gt;&lt;/article&gt;`);

        await sleep(1000);

        return res.end(`&lt;article&gt;&lt;p&gt;Third streamed comment.&lt;/p&gt;&lt;/article&gt;`);
    }

    res.writeHead(200, {
        "Content-Type": "text/html; charset=utf-8",
    });

    res.end(`
    &lt;!doctype html&gt;
    &lt;html&gt;
      &lt;head&gt;
        &lt;title&gt;Streaming HTML API Demo&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;h1&gt;Streaming HTML API Demo&lt;/h1&gt;

        &lt;button id="load-comments"&gt;Load comments&lt;/button&gt;

        &lt;section id="comments"&gt;&lt;/section&gt;

        &lt;p&gt;
          &lt;a href="/"&gt;Back to home&lt;/a&gt;
        &lt;/p&gt;

        &lt;script&gt;
          const button = document.querySelector("#load-comments")
          const comments = document.querySelector("#comments")

          button.addEventListener("click", async () =&gt; {
            const response = await fetch("/comments-stream")

            response.body
              .pipeThrough(new TextDecoderStream())
              .pipeTo(comments.streamHTMLUnsafe())
          })
        &lt;/script&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  `);
}

server.listen(3000, () =&gt; {
    console.log("Server running at http://localhost:3000");
});
</code></pre>
<p>This file creates the main demo server. It imports Node's built-in <code>http</code> module, then defines a small <code>sleep()</code> helper for delayed streaming, and then creates an HTTP server with three planned routes.</p>
<p>When the request URL matches <code>/normal-stream</code>, <code>/partial-updates-demo</code>, or <code>/stream-html-api-demo</code>, the server passes the request to the corresponding function. If the user visits the root page, the server returns a simple HTML page with links to the three demos.</p>
<p>Finally, <code>server.listen(3000)</code> starts the server on port <code>3000</code>, so you can open the demo in your browser at <code>http://localhost:3000</code>.</p>
<p>Run the server:</p>
<pre><code class="language-bash">npm run dev
</code></pre>
<p>Then open in browser:</p>
<pre><code class="language-txt">http://localhost:3000
</code></pre>
<p>You now have three demos.</p>
<h3 id="heading-test-the-normal-streaming-route">Test the Normal Streaming Route</h3>
<p>Open this route:</p>
<pre><code class="language-txt">http://localhost:3000/normal-stream
</code></pre>
<p>Watch the browser.</p>
<ul>
<li><p>The first paragraph appears early.</p>
</li>
<li><p>The second paragraph appears later.</p>
</li>
<li><p>The third paragraph appears last.</p>
</li>
</ul>
<p>This is useful, but it's still in-order streaming. The browser renders in the same order the server sends.</p>
<h3 id="heading-test-the-declarative-partial-updates-route">Test the Declarative Partial Updates Route</h3>
<p>Open this route:</p>
<pre><code class="language-txt">http://localhost:3000/partial-updates-demo
</code></pre>
<p>In a browser that supports the experimental feature, the page first shows three loading regions.</p>
<ul>
<li><p>After one second, the profile region updates.</p>
</li>
<li><p>After another second, the notifications region updates.</p>
</li>
<li><p>After two more seconds, the recommendations region updates.</p>
</li>
</ul>
<p>Notice the order. The recommendation section appears before the notification in the document. But the server sends the notification template before the recommendation template.</p>
<p>That's the point. The document structure and streaming order no longer need to be the same. The browser uses the <code>for</code> attribute on each <code>&lt;template&gt;</code> to find the matching processing instruction placeholder.</p>
<p>This example demonstrates the basic idea behind out-of-order HTML streaming.</p>
<h3 id="heading-test-the-streaming-html-api-route">Test the Streaming HTML API Route</h3>
<p>Open this route:</p>
<pre><code class="language-txt">http://localhost:3000/stream-html-api-demo
</code></pre>
<p>Click the button.</p>
<ul>
<li><p>The browser fetches <code>/comments-stream</code>.</p>
</li>
<li><p>The server sends comment HTML in chunks.</p>
</li>
<li><p>The client pipes the streamed response body into the <code>#comments</code> element.</p>
</li>
</ul>
<p>This isn't the same as declarative placeholder patching. Here, JavaScript starts the request and chooses the target element.</p>
<p>The new API improves how JavaScript inserts streamed HTML. The placeholder syntax improves how HTML itself declares later patches during document streaming. Keep those two ideas separate. They're related, but they solve different parts of the partial update story.</p>
<h2 id="heading-browser-support-and-current-status">Browser Support and Current Status</h2>
<p>Declarative Partial Updates are experimental. Chrome says these APIs are ready for developer testing from Chrome 148 through the experimental web platform features flag.</p>
<p>That means you should read this proposal as active platform work, not as stable production HTML.</p>
<p>To test native behavior in Chrome, enable this flag:</p>
<pre><code class="language-txt">chrome://flags/#enable-experimental-web-platform-features
</code></pre>
<p>Then restart the browser. The Blink developer thread describes the feature as a way to stream HTML content out of order and update an existing document with a stream of encoded patches.</p>
<p>The proposal also has a WICG explainer and a WHATWG HTML pull request.</p>
<p>That tells you where the proposal sits:</p>
<ol>
<li><p>It has a real implementation path in Chrome.</p>
</li>
<li><p>It has an explainer in WICG.</p>
</li>
<li><p>It has standardization discussion in WHATWG.</p>
</li>
<li><p>It's not a finished cross-browser standard yet.</p>
</li>
</ol>
<p>So Declarative Partial Updates are a proposed set of browser APIs for native HTML patching and streaming. Chrome has made them available for developer testing behind a flag.</p>
<h2 id="heading-security-sanitization-and-sharp-edges">Security, Sanitization, and Sharp Edges</h2>
<p>Partial updates may sound simple. But there are always security risks when adding HTML to a document.</p>
<p>HTML isn't just plain text. It can contain links, forms, event handlers, scripts, custom elements, and other elements that are parsed by the browser. So the source of the HTML is important.</p>
<p>If your server streams trusted HTML generated by your own template, the type of risk is different from the type of risk of adding HTML sent from a user's comments, profile, or chat message.</p>
<h3 id="heading-template-scope">Template Scope</h3>
<p>Chrome's article mentions an important restriction for <code>&lt;template for&gt;</code>. A template only updates processing instructions within the same parent element. Adding <code>&lt;template for&gt;</code> directly to the <code>&lt;body&gt;</code> gives it access to the whole document, including the <code>&lt;head&gt;</code>. That behavior is powerful, so you should understand the scope before designing around it.</p>
<p>The main point is simple: Don't assume a late template should patch any placeholder anywhere without rules. The patch target is scoped for security and predictability.</p>
<h3 id="heading-dynamic-insertion-has-different-behavior">Dynamic Insertion Has Different Behavior</h3>
<p>There's another sharp edge. If you insert a chunk of HTML dynamically using an API like <code>setHTML()</code> or <code>innerHTML</code>, the browser parses that HTML inside an intermediate fragment first.</p>
<p>That means declarative patching applies inside that parsed fragment. It doesn't automatically search the whole existing document and update old placeholders outside the fragment. This distinction matters because document streaming and dynamic HTML insertion aren't the same process.</p>
<h3 id="heading-safe-and-unsafe-html-apis">Safe and Unsafe HTML APIs</h3>
<p>The JavaScript insertion APIs also separate safer and lower-level methods.</p>
<ul>
<li><p><code>setHTML()</code> sanitizes the input before inserting it.</p>
</li>
<li><p><code>setHTMLUnsafe()</code> is lower level.</p>
</li>
</ul>
<p>MDN says <code>setHTML()</code> should almost always be preferred where supported because it always removes XSS-unsafe HTML entities.</p>
<p>So the rules are simple:</p>
<ul>
<li><p>Use safe APIs for untrusted content.</p>
</li>
<li><p>Treat unsafe APIs as advanced tools for trusted HTML or carefully sanitized HTML.</p>
</li>
<li><p>Never stream user-controlled HTML into the DOM without a sanitization strategy.</p>
</li>
</ul>
<h2 id="heading-what-this-means-for-web-developers">What This Means for Web Developers</h2>
<p>Declarative Partial Updates matter because they move a common framework pattern closer to the web platform. For years, developers have built partial update systems with JavaScript:</p>
<ul>
<li><p>Fetch HTML.</p>
</li>
<li><p>Find a DOM node.</p>
</li>
<li><p>Replace its content.</p>
</li>
<li><p>Keep loading states in sync.</p>
</li>
<li><p>Avoid breaking event handlers.</p>
</li>
<li><p>Handle errors.</p>
</li>
<li><p>Handle security.</p>
</li>
</ul>
<p>Frameworks and libraries improve this workflow. But the browser still lacks a small native language for saying: This late HTML belongs in that earlier placeholder. Declarative Partial Updates provide that language.</p>
<p>This doesn't mean JavaScript disappears. You still need JavaScript for interactivity, state, event handling, client-side data flows, and many app behaviors. This also doesn't mean frameworks disappear. React, HTMX, Astro, and similar tools solve larger problems than DOM patching.</p>
<p>The more realistic outcome is this:</p>
<ul>
<li><p>Frameworks and server-rendered tools may eventually use these primitives internally.</p>
</li>
<li><p>Smaller server-rendered apps may use them directly for simple loading regions.</p>
</li>
<li><p>Browsers may gain a cleaner, lower-level mechanism for streaming HTML into exact locations. That's the important part.</p>
</li>
</ul>
<p>The proposal doesn't make HTML a full app framework. It gives HTML a better streaming patch primitive.</p>
<h2 id="heading-when-this-feature-would-help">When This Feature Would Help</h2>
<p>Declarative Partial Updates fit pages where the server knows the layout early but some sections finish later.</p>
<p>Good examples include:</p>
<ul>
<li><p>Product pages with slow recommendation blocks.</p>
</li>
<li><p>Dashboards with independent data cards.</p>
</li>
<li><p>Search pages where results arrive from multiple services.</p>
</li>
<li><p>Documentation sites with heavy navigation or menus.</p>
</li>
<li><p>Profile pages with separate activity, stats, and notification regions.</p>
</li>
<li><p>Server-rendered apps that want loading states without client-side patch scripts.</p>
</li>
</ul>
<p>The most suitable is server-generated HTML. If your app already renders everything on the client, this proposal won't change your architecture much.</p>
<p>If your app emphasizes fast first renders, progressive streaming, low JavaScript overhead, and server-rendered UIs, this proposal becomes even more attractive.</p>
<h2 id="heading-when-you-should-avoid-it-today">When You Should Avoid It Today</h2>
<p>You should avoid production reliance on Declarative Partial Updates today.</p>
<p>Use it for:</p>
<ul>
<li><p>Learning.</p>
</li>
<li><p>Local demos.</p>
</li>
<li><p>Browser experiments.</p>
</li>
<li><p>Framework research.</p>
</li>
<li><p>Progressive enhancement experiments.</p>
</li>
</ul>
<p>Don't use this as the only path for important production UIs yet. Browser support isn't yet widespread enough. The recommendation may still change. Tooling isn't mature yet. Most users won't leave the experimental flag enabled. If you're experimenting with it, create a fallback.</p>
<p>For example, placeholders should still make sense on the server-rendered page even if they're not patched natively. Or, you should use a small JavaScript fallback in your app until browser support improves.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>HTML streaming is old. Out-of-order HTML patching is the new idea. Traditional streaming lets the browser render chunks as they arrive, but the document still follows the order of the response.</p>
<p>Declarative Partial Updates propose a native way to place late HTML into earlier placeholders.</p>
<p>The core syntax is small:</p>
<pre><code class="language-html">&lt;?marker name="target"&gt;

&lt;template for="target"&gt;
    &lt;p&gt;Late content&lt;/p&gt;
&lt;/template&gt;
</code></pre>
<p>For loading regions, the syntax uses a range:</p>
<pre><code class="language-html">&lt;?start name="target"&gt;
&lt;p&gt;Loading...&lt;/p&gt;
&lt;?end&gt;

&lt;template for="target"&gt;
    &lt;p&gt;Real content&lt;/p&gt;
&lt;/template&gt;
</code></pre>
<p>This gives HTML a browser-native patching model. It also opens the door for less custom JavaScript in some server-rendered streaming interfaces. But this is still experimental. The best way to think about Declarative Partial Updates is not HTML replaces frameworks.</p>
<p>A better way is this: HTML is gaining a low-level primitive that frameworks and server-rendered apps have needed for years.</p>
<p>If the proposal moves forward, web developers may get a cleaner way to build fast, progressively rendered pages where the server streams content as soon as each part is ready.</p>
<h2 id="heading-final-words"><strong>Final Words</strong></h2>
<p>If you found the information here valuable, feel free to share it with others who might benefit from it.</p>
<p>I’d really appreciate your thoughts – mention me on X&nbsp;<a href="https://x.com/sumit_analyzen">@sumit_analyzen</a>&nbsp;or on Facebook&nbsp;<a href="https://facebook.com/sumit.analyzen">@sumit.analyzen</a>,&nbsp;<a href="https://youtube.com/@logicBaseLabs">watch my coding tutorials</a>, or simply&nbsp;<a href="https://www.linkedin.com/in/sumitanalyzen/">connect with me on LinkedIn</a>.</p>
<p>You can also checkout my official website&nbsp;<a href="http://www.sumitsaha.me">www.sumitsaha.me</a>&nbsp;for more details about me.</p>
<h2 id="heading-sources">Sources</h2>
<ul>
<li><p><a href="https://developer.chrome.com/blog/declarative-partial-updates">Declarative Partial Updates, Chrome for Developers</a></p>
</li>
<li><p><a href="https://github.com/WICG/declarative-partial-updates">WICG Declarative Partial Updates Repository</a></p>
</li>
<li><p><a href="https://github.com/WICG/declarative-partial-updates/blob/main/patching-explainer.md">WICG Patching Explainer</a></p>
</li>
<li><p><a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/XHv8xd3MBdQ">Blink Dev, Ready for Developer Testing, Declarative Document Patching</a></p>
</li>
<li><p><a href="https://chromestatus.com/feature/5111042975465472">Chrome Platform Status, Declarative Document Patching</a></p>
</li>
<li><p><a href="https://github.com/whatwg/html/pull/11818">WHATWG HTML Pull Request for Template Patching</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/ProcessingInstruction">MDN, ProcessingInstruction</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template">MDN, HTML Template Element</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML">MDN, Element.setHTML</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTMLUnsafe">MDN, Element.setHTMLUnsafe</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API">MDN, HTML Sanitizer API</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Robotic Influencers of our Future: Experimenting With a Minecraft-playing, Twitch-streaming Robot ]]>
                </title>
                <description>
                    <![CDATA[ By Minja Axelsson In this article, I'll discuss how we reached young audiences by combining robotics with e-sports. What on Earth? Ever heard of anything like it before? Me neither. The robot was created as part of Futurice’s project with Yle, the na... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-robotic-influencers-of-our-future-a-minecraft-playing-twitch-streaming-robot/</link>
                <guid isPermaLink="false">66d460423a8352b6c5a2aaae</guid>
                
                    <category>
                        <![CDATA[ gaming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ minecraft ]]>
                    </category>
                
                    <category>
                        <![CDATA[ robotics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ streaming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitch ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 01 Oct 2019 21:26:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/10/twitchrobot.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Minja Axelsson</p>
<p>In this article, I'll discuss how we reached young audiences by combining robotics with e-sports.</p>
<h2 id="heading-what-on-earth">What on Earth?</h2>
<p>Ever heard of anything like it before? Me neither. The robot was created as part of <a target="_blank" href="https://www.futurice.com/">Futurice</a>’s project with <a target="_blank" href="https://yle.fi/">Yle</a>, the national broadcast company of Finland. </p>
<p>Yle produces content for TV, radio, and the web. It has a broad reach of older audiences, but has had trouble reaching younger ones. The goal of this project was to use new technology to reach young audiences — specifically teenagers.</p>
<p>Yle’s content has traditionally been non-participatory: the performers perform, and the audience watches. However, younger audiences generally view content that is more participatory, such as YouTube videos or streams. </p>
<p>We wanted to create participatory content — the performer should interact with their audience. Yle’s journalists specialized in teenage audiences pointed out that gaming and e-sports are popular content. We realized that gaming was the perfect context for this: the audience could play together with the performer. We wanted to explore what an entertainer, or even influencer of the future could look like. So why not create a streaming robot gamer?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/0_QW08nV9GgKS589PJ.png" alt="Image" width="600" height="400" loading="lazy">
<em>Yle’s journalists and Futurice’s roboticists coming up with the idea of a robot gamer</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/0_XdFOgvs4DhZVaHnW.png" alt="Image" width="600" height="400" loading="lazy">
<em>First drafts</em></p>
<h2 id="heading-what-was-this-robot-like">What Was This Robot Like?</h2>
<p>We chose two games for the robot to play: Flappy Bird and Minecraft.</p>
<p>Flappy Bird was a cult game that had a brief period of extreme popularity in 2014. We chose Flappy Bird because the mechanisms of the game are simple, and allowed for the game to be played with machine learning. We wanted to try a <a target="_blank" href="https://xviniette.github.io/FlappyLearning/">neuroevolution algorithm</a>, which evolved new birds into the game based on which birds did best in the previous generation. This way we could see how the audience reacted when a computer was playing the game.</p>
<p>We chose Minecraft for its communal features, which allow interaction between players. Players can co-operate or fight each other, trade with each other, and chat with each other. They can “grief” each other — i.e. be a nuisance. Players can also mine materials, and turn them into items, or even build cities. They can store precious things, farm the land, herd animals, and fight monsters. </p>
<p>Minecraft also has a material called redstone, which players can use to build logic. Effectively, players can build an entire computer inside of Minecraft. Poetic, eh?</p>
<p>For playing Minecraft, we decided that a human should control the robot. The game is complicated, and having authentic interactions with other players would require a human on the other end.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/0_zNxCX_zKJ3ikRM-y.png" alt="Image" width="600" height="400" loading="lazy">
<em>Flappy Bird and the robot</em></p>
<p>Our team of Yle’s journalists and Futurice roboticists defined some clear advantages for using a robot in this context:</p>
<ul>
<li>A robot is tireless — it can play forever, and provide content 24/7. It’s an ideal streamer.</li>
<li>A robot can be gender neutral. Gamers and gaming audiences are typically male, but a gender neutral robot could potentially attract more diverse audiences.</li>
<li>A robot can reflect gamers’ behaviour, stirring emotions. Gaming culture is often aggressive. The robot could be aggressive in turn, making gamers reflect on their own behaviour.</li>
<li>A robot can interact within the game and chat simultaneously. Humans are limited to one output, a robot can have several.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/1_-eTZqndK5kdFckQ5-lXpMg.jpeg" alt="Image" width="600" height="400" loading="lazy">
<em>Testing our streaming equipment and having problems with interference from the robot’s face screen.</em></p>
<p>We decided on a six hour gaming session, alternating between Minecraft and Flappy Bird. To nail down the user experience for the session, we defined guidelines for our design of the robot:</p>
<ul>
<li>Experimental user experience — the user should be able to explore within the interaction with the robot.</li>
<li>Streamers are usually strong characters. The robot is a character with a strong personality.</li>
<li>The robot can cause “WTF?” reactions in players. We wanted the experience to be memorable, rather than bland.</li>
<li>The culture and space of online gaming is unique, and should be designed for.</li>
</ul>
<p>Based on these guidelines, we created the character IQ_201. IQ_201 is based on aggressive online gamers that are convinced of their own superior intelligence (see <a target="_blank" href="https://knowyourmeme.com/memes/200-iq">this meme about having an IQ over 200</a>). The robot would be rude and reactive, meant to get a reaction out of adolescents interacting with it.</p>
<p>Before implementation, the team also wanted to take into account some ethical considerations:</p>
<ul>
<li>Transparency about how the robot is operated is needed. If this robot was going to be put into production, users should be able to find information on how it works.</li>
<li>The robot should treat all the players the same. This was also part of the decision to make the robot appear gender neutral. And due to the sometimes angry, hateful, even racist or sexist culture of gaming, we needed to design the robot’s personality carefully. It could be out-there, even rude, but never hateful. We didn’t want any heated gaming moments.</li>
<li>The robot should be rude, but not too over-the-top.</li>
<li>The chat needed to be moderated. As stated, gaming culture can be toxic. We wanted to keep a careful eye on both the Minecraft and Twitch chats, to ensure no shenanigans would go on.</li>
</ul>
<p>To fulfill all these requirements, we selected the <a target="_blank" href="https://www.furhatrobotics.com/">Furhat robot</a>. The Furhat robot had a relatively easy-to-use teleoperation interface, which allowed for the user to input text to be turned into the robot’s speech, as well as perform gestures at the click of a button.</p>
<h2 id="heading-flowers-and-violence">Flowers and Violence</h2>
<p>We had a 6 hour stream, starting at 2pm and ending at 8pm. We alternated between games: 2-3pm was Flappy Bird, 3-5pm Minecraft, 5-6pm Flappy Bird, then 6-8pm Minecraft again. This schedule allowed the robot operators playing Minecraft to have a much-needed break in the middle.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/twitchrobot-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Our robot</em></p>
<p>In the beginning, a few people joined. Gradually, we gained more and more people. We reached our peak at 4:20 — 49 simultaneous viewers on Twitch. Overall, we had 431 unique viewers. In Minecraft, there were around 30 active players. Considering how minimal our advertising was (one forum post and a few tweets), we were pleasantly surprised by the turnout.</p>
<p>The Minecraft sessions were directed by two robot operators (myself and another Minja). The other Minja played Minecraft, and I operated the robot’s voice and gestures. A third person was answering messages on chat.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/8kKPoYx7rMs" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Minecraft was overwhelming. The robot’s provocative character provoked teenagers into repeatedly killing it. After fleeing to the mountains to be with the llamas a couple of times and being killed yet again, we modified the robot’s behaviour to be more friendly. We wanted to create more constructive interactions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/0_pMMw-X8ZYwIOH_cU.png" alt="Image" width="600" height="400" loading="lazy">
<em>Two Minjas as robot operators</em></p>
<p>Toward the end of the second Minecraft stream, teenagers were cooperating with the robot. They protected it from the few aggressive players that were left, and gave it gifts such as flowers. Some even complimented the robot directly, to make it happy. The players started following the robot, agreeing to cooperate when it initiated building a lighthouse. They also built a house, and captured and named a llama: IQ_201 Junior.</p>
<p>There were two clear factions in the game: some were intent to kill the robot throughout the entire game, and some protected it throughout. Some became more comfortable with the robot as the stream went on, switching sides. Either way, the robot aroused strong emotions. Teenagers sought out genuine interaction with it. No-one ignored the robot, or was bored.</p>
<p>Discussions about how the robot worked were had throughout the stream. No-one asked the robot itself though, maybe out of respect or fear of annoying it. Discussions focused on whether the robot was “real”, i.e. whether it was truly automatic, or a human was operating it. Was it typing with actual physical hands? Or had it “hacked into” the game and was playing via code?</p>
<h2 id="heading-i-will-miss-you-robot">“I Will Miss You Robot!”</h2>
<p>16 people responded to our survey afterward. 80% of players were under 18, most were 13 to 15-year-olds. 80% of players interacted with the robot. This was extremely positive, we succeeded in making a robot that was compelling to users. 75% of players rated the robot as 3 or above, out of 5.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/0_7k1TlGnl986zyBKQ.png" alt="Image" width="600" height="400" loading="lazy">
<em>Viewers over time</em></p>
<p>We collected comments from the players from the survey, as well as the Minecraft game chat. They are translated from Finnish, and reflect some of the thoughts our players had on the robot.</p>
<h4 id="heading-the-critical-first">The critical first:</h4>
<p><strong>“It was pretty fun and cool. But it somehow felt like a set-up.” [Referring to the robot possibly having a human operator]</strong></p>
<p>A lot of players were wondering how the robot actually worked. This comment brings us back to the ethical consideration we talked about earlier: transparency about how the robot was operated. </p>
<p>Though we intended to be transparent at first, we decided to not inform users of the robot’s teleoperated nature for this first pilot. We made this choice because we wanted to keep the “suspension of disbelief” of the user active, meaning that we wanted the participants to play along with the fact they were talking to a “real robot” (an autonomous robot) (Duffy &amp; Zawieska, 2012). </p>
<p>The negative response we received regarding the unclarity on the robot’s functioning made it clear to us that if this pilot were extended, it is highly important to be more transparent. It is possible to hold suspension of disbelief and be honest of the robot’s operation simultaneously (we do all know that television shows are not reality, after all).</p>
<p><strong>“The robot was a bit simple in certain things, and sometimes talked meanly to people, and was condescending. This was a bit anxiety inducing… Was this intentional?”</strong></p>
<p>Some players felt that the robot’s rude behaviour crossed the boundary into condescending. They wished that the robot would be more considerate in the future. This indicates that even a robot can hurt feelings. In future versions, making IQ_201 more empathic, and less focused on the superiority of robots over humans, could have positive results.</p>
<p><strong>“The robot looked a bit blue in the face, and its voice was a bit weird.”</strong></p>
<p>Two teenagers did not enjoy the robot’s appearance and voice. One remarked especially about its blue face, asking why we did not make it “normal colored”. </p>
<p>This may have been due to the robot falling into the “uncanny valley” for these players. Uncanny valley is a theory developed by robotics researcher Masahiro Mori (Mori et al., 2012). His theory posits that as the appearance of a robot approaches human-likeness, there is a dip when the appearance gets very close. Zombies and corpses fall into this valley. </p>
<p>To get rid of this effect with our robot, it could be wise to alter the robot’s appearance and voice in future solutions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/0_g5B15D6hFSmG0bbi.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Uncanny Valley by Masahiro Mori. Wikimedia commons.</em></p>
<h4 id="heading-and-then-the-positive">And then the positive:</h4>
<p><strong>“It was really interesting gaming with the robot. :) Hopefully in the future we can have this type of event again. :)”</strong></p>
<p>The majority of the teenagers enjoyed playing with the robot, and watching the stream. Their feedback complimented the robot’s sense of humour, and its playing skills. A continuation of the pilot would surely find an interested audience.</p>
<p><strong>[to the robot] “Some people have trouble with new things. In this case, these players are having trouble with a robot, since you’re new.”</strong></p>
<p>This player consoled the robot in-game, as other players were giving it a hard time by continuously killing it. This comment was moving: the player felt bad for the robot, and thought the robot might feel bad as well, attempting to change that. This is a clear empathic response toward the robot.</p>
<p><strong>[to the robot] “I will miss you robot!”</strong></p>
<p>A few players gave the robot heartfelt goodbyes when it left Minecraft. These players found the robot approachable, and even formed an emotional bond with it. This means we succeeded in creating a compelling character, even during what was only a 6 hour stream. </p>
<p>For future builds of the robot, the players should be informed how the robot operates. This could help them calibrate an appropriate level of emotional bond to the robot.</p>
<h2 id="heading-are-robotic-influencers-our-future">Are Robotic Influencers Our Future?</h2>
<p>Players took an active interest in the robot. They approached it, interacted with it, and formed opinions on it. The robot also provoked emotional reactions — both positive and negative. Some participants really loved the robot and wished for more interaction in the future, and some were heavily critical. </p>
<p>This indicates that robot influencers have the capacity to affect our emotions — whether this capacity will ever reach the level of human entertainers, I do not know. Whether that is desirable, I also do not know.</p>
<p>What positively surprised me was that the young user base of the robot were media literate: they critically examined the robot’s mode of operation. The players had a good idea of what is possible with AI today, and what is not. They were not easily duped.</p>
<p>This makes me hopeful for our future, whether it contains robotic entertainers or not. When viewers remain critical, they can understand that robots are machines with no actual emotional capacity, even if viewers do choose to suspend disbelief. </p>
<p>Interacting with the robot can be though of as a form of <a target="_blank" href="https://en.wikipedia.org/wiki/Parasocial_interaction">parasocial interaction</a> for the viewer — where the viewer may feel that their relationship with the robot is close — even though it is not truly reciprocal. This in itself is not necessarily harmful, as long as we are honest about what the relationship truly is. We should understand that robots are putting on a performance to elicit emotions in us — like human entertainers do</p>
<p><a target="_blank" href="https://areena.yle.fi/1-50168989?source=post_page-----702735f678a8----------------------"><strong>A video about the project.</strong></a></p>
<p>First published on <a target="_blank" href="https://towardsdatascience.com/the-robotic-influencers-of-our-future-a-minecraft-playing-twitch-streaming-robot-702735f678a8">Towards Data Science.</a></p>
<hr>
<p><em>Duffy, B. R., &amp; Zawieska, K. (2012, September). Suspension of disbelief in social robotics. In 2012 IEEE RO-MAN: The 21st IEEE International Symposium on Robot and Human Interactive Communication (pp. 484–489). IEEE.</em></p>
<p><em>Mori, M., MacDorman, K. F., &amp; Kageki, N. (2012). The uncanny valley: The original essay by Masahiro Mori. IEEE Spectrum, 98–100.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to produce and consume data streams directly via Cypher with Streams Procedures ]]>
                </title>
                <description>
                    <![CDATA[ By Andrea Santurbano Leveraging Neo4j Streams — Part 3 This article is the third part of the Leveraging Neo4j Streams series (Part 1 is here, Part 2 is here). In it, I’ll show you how to bring Neo4j into your Apache Kafka flow by using the streams pr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-produce-and-consume-data-streams-directly-via-cypher-with-streams-procedures-52cbc5f543f1/</link>
                <guid isPermaLink="false">66c353fd5f85c1948b3fabaf</guid>
                
                    <category>
                        <![CDATA[ Apache Kafka ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Neo4j ]]>
                    </category>
                
                    <category>
                        <![CDATA[ streaming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 09 May 2019 17:13:07 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*47Ktwi-Gdj5S7keZpiteZA.gif" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Andrea Santurbano</p>
<h4 id="heading-leveraging-neo4j-streams-part-3">Leveraging Neo4j Streams — Part 3</h4>
<p>This article is the third part of the <strong>Leveraging Neo4j Streams</strong> series (Part 1 is <a target="_blank" href="https://medium.freecodecamp.org/how-to-leverage-neo4j-streams-and-build-a-just-in-time-data-warehouse-64adf290f093">here</a>, Part 2 is <a target="_blank" href="https://medium.freecodecamp.org/how-to-ingest-data-into-neo4j-from-a-kafka-stream-a34f574f5655">here</a>). In it, I’ll show you how to bring Neo4j into your <strong>Apache Kafka</strong> flow by using the streams procedures available with <a target="_blank" href="https://medium.com/neo4j/a-new-neo4j-integration-with-apache-kafka-6099c14851d2"><strong>Neo4j Streams</strong></a>.</p>
<p>In order to show how to integrate them, simplify the integration, and let you test the whole project by hand, I’ll use <a target="_blank" href="https://towardsdatascience.com/building-a-graph-data-pipeline-with-zeppelin-spark-and-neo4j-8b6b83f4fb70"><strong>Apache Zeppelin</strong></a><strong>, a notebook runner that simply allows you to <a target="_blank" href="https://towardsdatascience.com/building-a-graph-data-pipeline-with-zeppelin-spark-and-neo4j-8b6b83f4fb70">natively interact with Neo4j</a>.</strong></p>
<h3 id="heading-what-is-a-neo4j-stored-procedure">What is a Neo4j Stored Procedure?</h3>
<p>Starting from Neo4j 3.x, the concept of <a target="_blank" href="https://neo4j.com/docs/java-reference/current/extending-neo4j/procedures/"><strong>user-defined procedures and functions</strong></a> was introduced. These are custom implementations of certain functionalities and/or business rules that can’t be (easily) expressed in Cypher itself.</p>
<p>Neo4j provides a number of built-in procedures. The <a target="_blank" href="http://neo4j-contrib.github.io/neo4j-apoc-procedures/">APOC</a> library adds another 450 to cover all kinds of uses from data integration to graph refactorings.</p>
<h3 id="heading-what-are-the-streams-procedures">What are the streams procedures?</h3>
<p>The Neo4j Streams project comes out with two procedures:</p>
<ul>
<li><code>streams.publish</code>: allows custom message streaming from Neo4j to the configured environment by using the underlying configured Producer</li>
<li><code>streams.consume</code>: allows consuming messages from a given topic.</li>
</ul>
<h3 id="heading-set-up-the-environment">Set-Up the Environment</h3>
<p>Going to the following <a target="_blank" href="https://github.com/conker84/leveraging-neo4j-streams">Github repo</a>, you’ll find everything necessary in order to replicate what I’m presenting in this article. What you will need to start is <a target="_blank" href="https://docs.docker.com/"><strong>Docker</strong></a>, and then you can simply spin-up the stack by entering into the directory and from the Terminal execute the following command:</p>
<pre><code>$ docker-compose up
</code></pre><p>This will start-up the whole environment that comprises:</p>
<ul>
<li>Neo4j + Neo4j Streams module + APOC procedures</li>
<li>Apache Kafka</li>
<li>Apache Spark (which is not necessary in this article, but it’s used in the previous two)</li>
<li>Apache Zeppelin</li>
</ul>
<p>By going into Apache Zeppelin @ <code>http://localhost:8080</code> you’ll find in directory <code>Medium/Part 3</code> one notebook called “<strong>Streams Procedures</strong>” which is the subject of this article.</p>
<h3 id="heading-streamspublish">streams.publish</h3>
<p>This procedure allows custom message streaming from Neo4j to the configured environment by using the underlying configured Producer.</p>
<p>It takes two variables as input and returns nothing (as it sends its payload asynchronously to the stream):</p>
<ul>
<li><strong>topic</strong>, <em>type String</em>: where the data will be published</li>
<li><strong>payload</strong>, <em>type Object</em>: what you want to stream.</li>
</ul>
<p>Example:</p>
<pre><code>CALL streams.publish(<span class="hljs-string">'my-topic'</span>, <span class="hljs-string">'Hello World from Neo4j!'</span>)
</code></pre><p>The message retrieved from the Consumer is the following:</p>
<pre><code>{<span class="hljs-string">"payload"</span>: <span class="hljs-string">"Hello world from Neo4j!"</span>}
</code></pre><p>You can send any kind of data in the payload: <strong>nodes, relationships, paths, lists, maps, scalar values and nested versions thereof</strong>.</p>
<p>In case of nodes and/or relationships, if the topic is defined in the patterns provided by the Change Data Capture (CDC) configuration, their properties will be filtered according to the configuration.</p>
<p>Following is a simple video that shows the procedure in action:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/jmaPyKRDXsCEdwZEdeMzNyE2BoKXolGMEbjR" alt="Image" width="1665" height="820" loading="lazy">
<em>The streams.publish procedure in action</em></p>
<h3 id="heading-streamsconsume">streams.consume</h3>
<p>This procedure allows for consuming messages from a given topic.</p>
<p>It takes two variables as input:</p>
<ul>
<li><strong>topic</strong>, <em>type String</em>: where you want to consume the data</li>
<li><strong>config</strong>, _type Map: the configuration parameters</li>
</ul>
<p>and returns a list of collected events.</p>
<p>The <strong>config</strong> params are:</p>
<ul>
<li><strong>timeout</strong>, <em>type Long</em>: it’s the value passed to Kafka <code>[Consumer#poll](https://kafka.apache.org/10/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#poll-long-)</code> method (milliseconds). Default 1000.</li>
<li><strong>from</strong>, <em>type String</em>: it’s the Kafka configuration parameter <code>auto.offset.reset</code></li>
</ul>
<p>Use:</p>
<pre><code>CALL streams.consume(<span class="hljs-string">'my-topic'</span>, {<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">config</span>&gt;</span>}) YIELD event RETURN event</span>
</code></pre><p>Example: Imagine you have a producer that publishes events like this:</p>
<pre><code>{<span class="hljs-string">"name"</span>: <span class="hljs-string">"Andrea"</span>, <span class="hljs-string">"surname"</span>: <span class="hljs-string">"Santurbano"</span>}
</code></pre><p>We can create user nodes in this way:</p>
<pre><code>CALL streams.consume(<span class="hljs-string">'my-topic'</span>, {<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">config</span>&gt;</span>}) YIELD eventCREATE (p:Person{firstName: event.data.name, lastName: event.data.surname})</span>
</code></pre><p>Following is a simple video that shows the procedure in action:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/m0Lui2cBqiT0OQO9DTuiQfOYHZmpAKwRB4Ys" alt="Image" width="1665" height="818" loading="lazy">
<em>The stream.consume procedure in action</em></p>
<p>So this is the end of the “Leveraging Neo4j Streams” series, I hope you enjoyed it!</p>
<p>If you have already tested the Neo4j-Streams module or tested it via this notebook, please fill out our <a target="_blank" href="https://goo.gl/forms/VLwvqwsIvdfdm9fL2"><strong>feedback survey</strong></a>.</p>
<p>If you run into any issues or have thoughts about improving our work, <a target="_blank" href="http://github.com/neo4j-contrib/neo4j-streams/issues">please raise a GitHub issue</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to leverage Neo4j Streams and build a just-in-time data warehouse ]]>
                </title>
                <description>
                    <![CDATA[ By Andrea Santurbano In this article, we’ll show how to create a Just-In-Time Data Warehouse by using Neo4j and the Neo4j Streams module with Apache Spark’s Structured Streaming Apis and Apache Kafka. In order to show how to integrate them, simplify ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-leverage-neo4j-streams-and-build-a-just-in-time-data-warehouse-64adf290f093/</link>
                <guid isPermaLink="false">66c3531b0107ba195e79f72a</guid>
                
                    <category>
                        <![CDATA[ Apache Kafka ]]>
                    </category>
                
                    <category>
                        <![CDATA[ kafka ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Neo4j ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ streaming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 29 Jan 2019 16:37:47 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*lwaAjWM8LuAvRZ1T67vWQw.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Andrea Santurbano</p>
<p>In this article, we’ll show how to create a <a target="_blank" href="https://databricks.com/blog/2015/11/30/building-a-just-in-time-data-warehouse-platform-with-databricks.html">Just-In-Time Data Warehouse</a> by using <a target="_blank" href="https://neo4j.com/"><strong>Neo4j</strong></a> <strong>and the <a target="_blank" href="https://medium.com/neo4j/a-new-neo4j-integration-with-apache-kafka-6099c14851d2">Neo4j Streams</a></strong> module with <strong>Apache Spark</strong>’s Structured Streaming Apis and <strong>Apache Kafka.</strong></p>
<p>In order to show how to integrate them, simplify the integration, and let you test the whole project by hand, I’ll use <a target="_blank" href="https://towardsdatascience.com/building-a-graph-data-pipeline-with-zeppelin-spark-and-neo4j-8b6b83f4fb70"><strong>Apache Zeppelin</strong></a> <strong>a notebook runner that simply allows to <a target="_blank" href="https://towardsdatascience.com/building-a-graph-data-pipeline-with-zeppelin-spark-and-neo4j-8b6b83f4fb70">natively interact with Neo4j</a>.</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/qrtYkmywS6MwhLmsVKauFIQHuuu2vKwNUmp7" alt="Image" width="800" height="339" loading="lazy">
<em>The final result: how a kafka event streamed by Neo4j gets collected by Apache Spark</em></p>
<h3 id="heading-leveraging-neo4j-streams">Leveraging Neo4j Streams</h3>
<p>The Neo4j Streams project is composed of three main pillars:</p>
<ul>
<li>The <strong>Change Data Capture</strong> (the subject of this first article) that allows us to stream database changes over Kafka topics</li>
<li>The <strong>Sink</strong> that allows consuming data streams from the Kafka topic</li>
<li>A <strong>set of procedures</strong> that allows us to Produce/Consume data to/from Kafka Topics</li>
</ul>
<h3 id="heading-what-is-a-change-data-capture">What is a Change Data Capture?</h3>
<p>It’s a system that automatically captures changes from a source system (a Database, for instance) and automatically provides these changes to downstream systems for a variety of use cases.</p>
<p>CDC typically forms part of an ETL pipeline. This is an important component for ensuring Data Warehouses (DWH) are kept up to date with any record changes.</p>
<p>Also traditionally CDC applications used to work off of transaction logs, thereby allowing us to replicate databases without having much of a performance impact on its operation.</p>
<h3 id="heading-how-does-the-neo4j-streams-cdc-module-deal-with-database-changes">How does the Neo4j Streams CDC module deal with database changes?</h3>
<p>Every transaction inside Neo4j gets captured and transformed in order to stream an atomic element of the transaction.</p>
<p>Let’s suppose we have a simple creation of two nodes and one relationship between them:</p>
<pre><code>CREATE (andrea:Person{<span class="hljs-attr">name</span>:<span class="hljs-string">"Andrea"</span>})-[knows:KNOWS{<span class="hljs-attr">since</span>:<span class="hljs-number">2014</span>}]-&amp;gt;(michael:Person{<span class="hljs-attr">name</span>:<span class="hljs-string">"Michael"</span>})
</code></pre><p>The CDC module will transform this transaction into 3 events (2 node creation, 1 relationship creation).</p>
<p>The Event structure was inspired by the <a target="_blank" href="https://debezium.io/">Debezium</a> format and has the following general structure:</p>
<pre><code>{  <span class="hljs-string">"meta"</span>: { <span class="hljs-comment">/* transaction meta-data */</span> },  <span class="hljs-string">"payload"</span>: { <span class="hljs-comment">/* the data related to the transaction */</span>    <span class="hljs-string">"before"</span>: { <span class="hljs-comment">/* the data before the transaction */</span>},    <span class="hljs-string">"after"</span>: { <span class="hljs-comment">/* the data after the transaction */</span>}  }}
</code></pre><p>Node source <code>(andrea)</code>:</p>
<pre><code>{  <span class="hljs-string">"meta"</span>: {    <span class="hljs-string">"timestamp"</span>: <span class="hljs-number">1532597182604</span>,    <span class="hljs-string">"username"</span>: <span class="hljs-string">"neo4j"</span>,    <span class="hljs-string">"tx_id"</span>: <span class="hljs-number">1</span>,    <span class="hljs-string">"tx_event_id"</span>: <span class="hljs-number">0</span>,    <span class="hljs-string">"tx_events_count"</span>: <span class="hljs-number">3</span>,    <span class="hljs-string">"operation"</span>: <span class="hljs-string">"created"</span>,    <span class="hljs-string">"source"</span>: {      <span class="hljs-string">"hostname"</span>: <span class="hljs-string">"neo4j.mycompany.com"</span>    }  },  <span class="hljs-string">"payload"</span>: {    <span class="hljs-string">"id"</span>: <span class="hljs-string">"1004"</span>,    <span class="hljs-string">"type"</span>: <span class="hljs-string">"node"</span>,    <span class="hljs-string">"after"</span>: {      <span class="hljs-string">"labels"</span>: [<span class="hljs-string">"Person"</span>],      <span class="hljs-string">"properties"</span>: {        <span class="hljs-string">"name"</span>: <span class="hljs-string">"Andrea"</span>      }    }  }}
</code></pre><p>Node target <code>(michael)</code>:</p>
<pre><code>{  <span class="hljs-string">"meta"</span>: {    <span class="hljs-string">"timestamp"</span>: <span class="hljs-number">1532597182604</span>,    <span class="hljs-string">"username"</span>: <span class="hljs-string">"neo4j"</span>,    <span class="hljs-string">"tx_id"</span>: <span class="hljs-number">1</span>,    <span class="hljs-string">"tx_event_id"</span>: <span class="hljs-number">1</span>,    <span class="hljs-string">"tx_events_count"</span>: <span class="hljs-number">3</span>,    <span class="hljs-string">"operation"</span>: <span class="hljs-string">"created"</span>,    <span class="hljs-string">"source"</span>: {      <span class="hljs-string">"hostname"</span>: <span class="hljs-string">"neo4j.mycompany.com"</span>    }  },  <span class="hljs-string">"payload"</span>: {    <span class="hljs-string">"id"</span>: <span class="hljs-string">"1006"</span>,    <span class="hljs-string">"type"</span>: <span class="hljs-string">"node"</span>,    <span class="hljs-string">"after"</span>: {      <span class="hljs-string">"labels"</span>: [<span class="hljs-string">"Person"</span>],      <span class="hljs-string">"properties"</span>: {        <span class="hljs-string">"name"</span>: <span class="hljs-string">"Michael"</span>      }    }  }}
</code></pre><p>Relationship <code>knows</code>:</p>
<pre><code>{  <span class="hljs-string">"meta"</span>: {    <span class="hljs-string">"timestamp"</span>: <span class="hljs-number">1532597182604</span>,    <span class="hljs-string">"username"</span>: <span class="hljs-string">"neo4j"</span>,    <span class="hljs-string">"tx_id"</span>: <span class="hljs-number">1</span>,    <span class="hljs-string">"tx_event_id"</span>: <span class="hljs-number">2</span>,    <span class="hljs-string">"tx_events_count"</span>: <span class="hljs-number">3</span>,    <span class="hljs-string">"operation"</span>: <span class="hljs-string">"created"</span>,    <span class="hljs-string">"source"</span>: {      <span class="hljs-string">"hostname"</span>: <span class="hljs-string">"neo4j.mycompany.com"</span>    }  },  <span class="hljs-string">"payload"</span>: {    <span class="hljs-string">"id"</span>: <span class="hljs-string">"1007"</span>,    <span class="hljs-string">"type"</span>: <span class="hljs-string">"relationship"</span>,    <span class="hljs-string">"label"</span>: <span class="hljs-string">"KNOWS"</span>,    <span class="hljs-string">"start"</span>: {      <span class="hljs-string">"labels"</span>: [<span class="hljs-string">"Person"</span>],      <span class="hljs-string">"id"</span>: <span class="hljs-string">"1005"</span>    },    <span class="hljs-string">"end"</span>: {      <span class="hljs-string">"labels"</span>: [<span class="hljs-string">"Person"</span>],      <span class="hljs-string">"id"</span>: <span class="hljs-string">"106"</span>    },    <span class="hljs-string">"after"</span>: {      <span class="hljs-string">"properties"</span>: {        <span class="hljs-string">"since"</span>: <span class="hljs-number">2014</span>      }    }  }}
</code></pre><p>By default, all the data will be streamed on the <code>neo4j</code> topic. The CDC module allows controlling which nodes are sent to Kafka, and which of their properties you want to send to the topic:</p>
<pre><code>streams.source.topic.nodes.&lt;TOPIC_NAME&gt;=<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">PATTERN</span>&gt;</span></span>
</code></pre><p>With the following example:</p>
<pre><code>streams.source.topic.nodes.products=Product{name, code}
</code></pre><p>The CDC module will send to the <code>products</code> topic all the nodes that have the label <code>Product</code>. It then sends, to that topic, only the changes about <code>name</code> and <code>code</code> properties. Please go the official documentation for a full description on <a target="_blank" href="https://neo4j-contrib.github.io/neo4j-streams/#_patterns">how label filtering works</a>.</p>
<p>For a more in-depth description of the Neo4j Streams project and how/why we at <a target="_blank" href="http://www.larus-ba.it/"><strong>LARUS</strong></a> and <a target="_blank" href="https://neo4j.com/"><strong>Neo4j</strong></a> built it, check out this article that provides an <a target="_blank" href="https://medium.com/neo4j/a-new-neo4j-integration-with-apache-kafka-6099c14851d2">in-depth description</a>.</p>
<h3 id="heading-beyond-the-traditional-data-warehouse">Beyond the traditional Data Warehouse</h3>
<p>A traditional DWH requires data teams to constantly build multiple costly and time-consuming Extract Transform Load (ETL) pipelines to ultimately derive business insights.</p>
<p>One of the biggest pain points is that, due to its <strong>rigid architecture that’s difficult to change</strong>, Enterprise Data Warehouses are <strong>inherently rigid.</strong> That’s because:</p>
<ul>
<li>they are <strong>based on the</strong> <strong>Schema-On-Write architecture:</strong> first, you define your schema, then you write your data, then you read your data and it comes back in the schema you defined up-front</li>
<li>they are <strong>based</strong> on (expensive) <strong>batched/scheduled jobs</strong></li>
</ul>
<p><strong>This results in having to build costly and time-consuming ETL pipelines</strong> to access and manipulate the data. And as <strong>new data types</strong> and sources are introduced, the need to augment your ETL pipelines <strong>exacerbates the problem</strong>.</p>
<p>Thanks to the <strong>combination</strong> of the stream data processing with the <strong>Neo4j Streams CDC module</strong> and the <strong>Schema-On-Read</strong> approach provided by Apache Spark, we can <strong>overcome this rigidity</strong> and build a new kind of (flexible) DWH.</p>
<h3 id="heading-a-paradigm-shift-just-in-time-data-warehouse">A paradigm shift: Just-In-Time Data Warehouse</h3>
<p>A JIT-DWH solution is designed to easily handle a wider variety of data from different sources and starts from a different approach about how to deal with and manage data: <strong>Schema-On-Read.</strong></p>
<h3 id="heading-schema-on-read">Schema-On-Read</h3>
<p><a target="_blank" href="https://www.marklogic.com/blog/schema-on-read-vs-schema-on-write/">Schema-On-Read</a> follows a different sequence: <strong>it just loads the data as-is and applies your own lens to the data when you read it back out</strong>. With this kind of approach, you can present data in a schema that is adapted best to the queries being issued. You’re not stuck with a one-size-fits-all schema. With schema-on-read, you can present the data back in a schema that is most relevant to the task at hand.</p>
<h4 id="heading-set-up-the-environment">Set-Up the Environment</h4>
<p>Going to the following <a target="_blank" href="https://github.com/conker84/leveraging-neo4j-streams"><strong>Github repo</strong></a> you’ll find everything you need in order to replicate what I’m presenting in this article. What you will need to start is <a target="_blank" href="https://docs.docker.com/"><strong>Docker</strong></a><strong>.</strong> Then you can simply spin-up the stack by entering into the directory and from the Terminal, executing the following command:</p>
<pre><code>$ docker-compose up
</code></pre><p>This will start-up the whole environment that comprises:</p>
<ul>
<li>Neo4j + Neo4j Streams module + APOC procedures</li>
<li>Apache Kafka</li>
<li>Apache Spark</li>
<li>Apache Zeppelin</li>
</ul>
<p><img src="https://cdn-media-1.freecodecamp.org/images/j4n2GGDDTdZZFuoNuyP9eioEs8C4aAk5hfg0" alt="Image" width="800" height="213" loading="lazy">
<em>The whole architecture based on Docker containers</em></p>
<p>By going into Apache Zeppelin @ <code>http://localhost:8080</code> you’ll find in the directory <code>Medium/Part 1</code> two notebooks:</p>
<ul>
<li><strong>Create a Just-In-Time Data Warehouse</strong>: in this notebook, we will build the JIT-DWH</li>
<li><strong>Query The JIT-DWH</strong>: in this notebook, we will perform some queries over the JIT-DWH</li>
</ul>
<h3 id="heading-the-use-case">The Use-Case:</h3>
<p>We’ll create a fake social network like dataset. This will activate the CDC module of Neo4j Stream, and via Apache Spark we’ll intercept this event and persist them on the File System as JSON.</p>
<p>Then we’ll demonstrate how new fields added in our nodes will be automatically added to our JIT-DWL without the modification of the ETL pipeline, thanks to the Schema-On-Read approach.</p>
<p>We’ll execute the following steps:</p>
<ol>
<li>Create the fake data set</li>
<li>Build our data pipeline that intercepts the Kafka events published by the Neo4j Streams CDC module</li>
<li>Make the first query over our JIT-DWH on Spark</li>
<li>Add a new field in our graph model</li>
<li>Show how the new field is automatically exposed in real time thanks to the Neo4j Streams CDC module (without the need for changes over our ETL pipeline thanks to the Schema-On-Read approach).</li>
</ol>
<h3 id="heading-notebook-1-create-a-just-in-time-data-warehouse">Notebook 1: Create a Just-In-Time Data Warehouse</h3>
<p>We’ll create a fake social network by using the APOC <code>apoc.periodic.repeat</code> procedure that executes this query every 15 seconds:</p>
<pre><code>WITH [<span class="hljs-string">"M"</span>, <span class="hljs-string">"F"</span>, <span class="hljs-string">""</span>] AS genderUNWIND range(<span class="hljs-number">1</span>, <span class="hljs-number">10</span>) AS idCREATE (p:Person {<span class="hljs-attr">id</span>: apoc.create.uuid(), <span class="hljs-attr">name</span>: <span class="hljs-string">"Name-"</span> +  apoc.text.random(<span class="hljs-number">10</span>), <span class="hljs-attr">age</span>: round(rand() * <span class="hljs-number">100</span>), <span class="hljs-attr">index</span>: id, <span class="hljs-attr">gender</span>: gender[toInteger(size(gender) * rand())]})WITH collect(p) AS peopleUNWIND people AS p1UNWIND range(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>) AS friendWITH p1, people[(p1.index + friend) % size(people)] AS p2CREATE (p1)-[:KNOWS{<span class="hljs-attr">years</span>: round(rand() * <span class="hljs-number">10</span>), <span class="hljs-attr">engaged</span>: (rand() &gt; <span class="hljs-number">0.5</span>)}]-&amp;gt;(p2)
</code></pre><p>If you need more details about the APOC project, please follow this <a target="_blank" href="https://neo4j-contrib.github.io/neo4j-apoc-procedures/">link</a>.</p>
<p>So the resulting graph model is quite straightforward:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ZxNqMi-FAYLjHLNr7hFEJ21Bdoq6UtA2Qus2" alt="Image" width="95" height="165" loading="lazy">
<em>The Graph Model</em></p>
<p>Let’s create an index over the Person node:</p>
<pre><code>%neo4jCREATE INDEX ON :Person(id)
</code></pre><p>Now let’s set the Background Job in Neo4j:</p>
<pre><code>%neo4jCALL apoc.periodic.repeat(<span class="hljs-string">'create-fake-social-data'</span>, <span class="hljs-string">'WITH ["M", "F", "X"] AS gender UNWIND range(1, 10) AS id CREATE (p:Person {id: apoc.create.uuid(), name: "Name-" +  apoc.text.random(10), age: round(rand() * 100), index: id, gender: gender[toInteger(size(gender) * rand())]}) WITH collect(p) AS people UNWIND people AS p1 UNWIND range(1, 3) AS friend WITH p1, people[(p1.index + friend) % size(people)] AS p2 CREATE (p1)-[:KNOWS{years: round(rand() * 10), engaged: (rand() &gt; 0.5)}]-&gt;(p2)'</span>, <span class="hljs-number">15</span>) YIELD nameRETURN name AS created
</code></pre><p>This background query brings the Neo4j-Streams CDC module to stream related events over the “neo4j” Kafka topic (the default topic of the CDC).</p>
<p>Now let’s create a Structured Streaming Dataset that consumes the data from the “neo4j” topic:</p>
<pre><code>val kafkaStreamingDF = (spark    .readStream    .format(<span class="hljs-string">"kafka"</span>)    .option(<span class="hljs-string">"kafka.bootstrap.servers"</span>, <span class="hljs-string">"broker:9093"</span>)    .option(<span class="hljs-string">"startingoffsets"</span>, <span class="hljs-string">"earliest"</span>)    .option(<span class="hljs-string">"subscribe"</span>, <span class="hljs-string">"neo4j"</span>)    .load())
</code></pre><p>The <code>kafkaStreamingDF</code> Dataframe is basically a <code>ProducerRecord</code> representation. And in fact its schema is:</p>
<pre><code>root|-- key: binary (nullable = <span class="hljs-literal">true</span>)|-- value: binary (nullable = <span class="hljs-literal">true</span>)|-- topic: string (nullable = <span class="hljs-literal">true</span>)|-- partition: integer (nullable = <span class="hljs-literal">true</span>)|-- offset: long (nullable = <span class="hljs-literal">true</span>)|-- timestamp: timestamp (nullable = <span class="hljs-literal">true</span>)|-- timestampType: integer (nullable = <span class="hljs-literal">true</span>)
</code></pre><p>Now let’s create the Structure of the data streamed by the CDC using the Spark APIs in order to read the streamed data:</p>
<pre><code>val cdcMetaSchema = (<span class="hljs-keyword">new</span> StructType()    .add(<span class="hljs-string">"timestamp"</span>, LongType)    .add(<span class="hljs-string">"username"</span>, StringType)    .add(<span class="hljs-string">"operation"</span>, StringType)    .add(<span class="hljs-string">"source"</span>, MapType(StringType, StringType, <span class="hljs-literal">true</span>)))    val cdcPayloadSchemaBeforeAfter = (<span class="hljs-keyword">new</span> StructType()    .add(<span class="hljs-string">"labels"</span>, ArrayType(StringType, <span class="hljs-literal">false</span>))    .add(<span class="hljs-string">"properties"</span>, MapType(StringType, StringType, <span class="hljs-literal">true</span>)))    val cdcPayloadSchema = (<span class="hljs-keyword">new</span> StructType()    .add(<span class="hljs-string">"id"</span>, StringType)    .add(<span class="hljs-string">"type"</span>, StringType)    .add(<span class="hljs-string">"label"</span>, StringType)    .add(<span class="hljs-string">"start"</span>, MapType(StringType, StringType, <span class="hljs-literal">true</span>))    .add(<span class="hljs-string">"end"</span>, MapType(StringType, StringType, <span class="hljs-literal">true</span>))    .add(<span class="hljs-string">"before"</span>, cdcPayloadSchemaBeforeAfter)    .add(<span class="hljs-string">"after"</span>, cdcPayloadSchemaBeforeAfter))    val cdcSchema = (<span class="hljs-keyword">new</span> StructType()    .add(<span class="hljs-string">"meta"</span>, cdcMetaSchema)    .add(<span class="hljs-string">"payload"</span>, cdcPayloadSchema))
</code></pre><p>The <code>cdcSchema</code> is suitable for both node and relationships events.</p>
<p>What we need now is to extract only the CDC event from the Dataframe, so let’s perform a simple transformation query over Spark:</p>
<pre><code>val cdcDataFrame = (kafkaStreamingDF    .selectExpr(<span class="hljs-string">"CAST(value AS STRING) AS VALUE"</span>)    .select(from_json(<span class="hljs-string">'VALUE, cdcSchema) as '</span><span class="hljs-built_in">JSON</span>))
</code></pre><p>The <code>cdcDataFrame</code> contains just one column <strong>JSON</strong> which is the data streamed from the Neo4j-Streams CDC module.</p>
<p>Let’s perform a simple ETL query in order to extract fields of interest:</p>
<pre><code>val dataWarehouseDataFrame = (cdcDataFrame    .where(<span class="hljs-string">"json.payload.type = 'node' and (array_contains(nvl(json.payload.after.labels, json.payload.before.labels), 'Person'))"</span>)    .selectExpr(<span class="hljs-string">"json.payload.id AS neo_id"</span>, <span class="hljs-string">"CAST(json.meta.timestamp / 1000 AS Timestamp) AS timestamp"</span>,        <span class="hljs-string">"json.meta.source.hostname AS host"</span>,        <span class="hljs-string">"json.meta.operation AS operation"</span>,        <span class="hljs-string">"nvl(json.payload.after.labels, json.payload.before.labels) AS labels"</span>,        <span class="hljs-string">"explode(json.payload.after.properties)"</span>))
</code></pre><p>This query is quite important, because it represents how the data will be persisted over the filesystem. Every node will be <strong>exploded</strong> in a number of JSON snippets, one for each node property, just like this:</p>
<pre><code>{<span class="hljs-string">"neo_id"</span>:<span class="hljs-string">"35340"</span>,<span class="hljs-string">"timestamp"</span>:<span class="hljs-string">"2018-12-19T23:07:10.465Z"</span>,<span class="hljs-string">"host"</span>:<span class="hljs-string">"neo4j"</span>,<span class="hljs-string">"operation"</span>:<span class="hljs-string">"created"</span>,<span class="hljs-string">"labels"</span>:[<span class="hljs-string">"Person"</span>],<span class="hljs-string">"key"</span>:<span class="hljs-string">"name"</span>,<span class="hljs-string">"value"</span>:<span class="hljs-string">"Name-5wc62uKO5l"</span>}
</code></pre><pre><code>{<span class="hljs-string">"neo_id"</span>:<span class="hljs-string">"35340"</span>,<span class="hljs-string">"timestamp"</span>:<span class="hljs-string">"2018-12-19T23:07:10.465Z"</span>,<span class="hljs-string">"host"</span>:<span class="hljs-string">"neo4j"</span>,<span class="hljs-string">"operation"</span>:<span class="hljs-string">"created"</span>,<span class="hljs-string">"labels"</span>:[<span class="hljs-string">"Person"</span>],<span class="hljs-string">"key"</span>:<span class="hljs-string">"index"</span>,<span class="hljs-string">"value"</span>:<span class="hljs-string">"8"</span>}
</code></pre><pre><code>{<span class="hljs-string">"neo_id"</span>:<span class="hljs-string">"35340"</span>,<span class="hljs-string">"timestamp"</span>:<span class="hljs-string">"2018-12-19T23:07:10.465Z"</span>,<span class="hljs-string">"host"</span>:<span class="hljs-string">"neo4j"</span>,<span class="hljs-string">"operation"</span>:<span class="hljs-string">"created"</span>,<span class="hljs-string">"labels"</span>:[<span class="hljs-string">"Person"</span>],<span class="hljs-string">"key"</span>:<span class="hljs-string">"id"</span>,<span class="hljs-string">"value"</span>:<span class="hljs-string">"944e58bf-0cf7-49cf-af4a-c803d44f222a"</span>}
</code></pre><pre><code>{<span class="hljs-string">"neo_id"</span>:<span class="hljs-string">"35340"</span>,<span class="hljs-string">"timestamp"</span>:<span class="hljs-string">"2018-12-19T23:07:10.465Z"</span>,<span class="hljs-string">"host"</span>:<span class="hljs-string">"neo4j"</span>,<span class="hljs-string">"operation"</span>:<span class="hljs-string">"created"</span>,<span class="hljs-string">"labels"</span>:[<span class="hljs-string">"Person"</span>],<span class="hljs-string">"key"</span>:<span class="hljs-string">"gender"</span>,<span class="hljs-string">"value"</span>:<span class="hljs-string">"F"</span>}
</code></pre><p>This kind of structure can be easily turned into tabular representation (we’ll see in the next few steps how to do this).</p>
<p>Now let's write a Spark continuous streaming query that saves the data to the file system as JSON:</p>
<pre><code>val writeOnDisk = (dataWarehouseDataFrame    .writeStream    .format(<span class="hljs-string">"json"</span>)    .option(<span class="hljs-string">"checkpointLocation"</span>, <span class="hljs-string">"/zeppelin/spark-warehouse/jit-dwh/checkpoint"</span>)    .option(<span class="hljs-string">"path"</span>, <span class="hljs-string">"/zeppelin/spark-warehouse/jit-dwh"</span>)    .queryName(<span class="hljs-string">"nodes"</span>)    .start())
</code></pre><p>We have now created a simple JIT-DWH. In the second notebook we’ll learn how to query it and how simple it is to deal with dynamical changes in the data structures thanks schema-on-read.</p>
<h3 id="heading-notebook-2-query-the-jit-dwh">Notebook 2: Query The JIT-DWH</h3>
<p>The first paragraph let us query and display our JIT-DWH</p>
<pre><code>val flattenedDF = (spark.read.format(<span class="hljs-string">"json"</span>).load(<span class="hljs-string">"/zeppelin/spark-warehouse/jit-dwh/**"</span>)    .where(<span class="hljs-string">"neo_id is not null"</span>)    .groupBy(<span class="hljs-string">"neo_id"</span>, <span class="hljs-string">"timestamp"</span>, <span class="hljs-string">"host"</span>, <span class="hljs-string">"labels"</span>, <span class="hljs-string">"operation"</span>)    .pivot(<span class="hljs-string">"key"</span>)    .agg(first($<span class="hljs-string">"value"</span>)))z.show(flattenedDF)
</code></pre><p>Remember how we saved the data in JSON some row above? The <code>flattenedDF</code> simply pivoted the JSONs over the <code>key</code> field thus grouping the data over 5 columns that represent the “unique key” (_“neo<em>id”, “timestamp”, “host”, “labels”, “operation”</em>). This allows us to have this tabular representation of the source data as follows:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/XA4vskTWdUra50ncym941E78zHZcwGM6TY4q" alt="Image" width="800" height="277" loading="lazy">
<em>The result of the query</em></p>
<p>Now imagine that our Person dataset gets a new field: <strong>birth.</strong> Let's add this new field to one node; in this case, you must choose an id from your dataset and update it with the following paragraph:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/onz1jNYkzyiaEAFAcYi-4f2lay8rtqs55PBM" alt="Image" width="800" height="276" loading="lazy">
<em>Just fill the form with your data and execute the paragraph</em></p>
<p>Now the final step: reuse the same query and filter the DWH by the id that we have previously changed in order to check how our dataset changed according to the changes made over Neo4j.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/dTgF8U8-F--yYJOUm3BLa70MaOd0E5XAKnaH" alt="Image" width="800" height="339" loading="lazy">
<em>The birth field is present without changes to our queries</em></p>
<h3 id="heading-conclusions">Conclusions</h3>
<p>In this first part, we learned how to leverage the events produced by Neo4j Stream CDC module in order to build a simple (Real-Time) JIT-DWL that uses the Schema-On-Read approach.</p>
<p>In Part 2 we’ll discover how to use the Sink module in order to ingest data into Neo4j directly from Kafka.</p>
<p>If you have already tested the Neo4j-Streams module or tested it via these notebooks please fill out our <a target="_blank" href="https://goo.gl/forms/VLwvqwsIvdfdm9fL2"><strong>feedback survey</strong></a>.</p>
<p>If you run into any issues or have thoughts about improving our work, <a target="_blank" href="http://github.com/neo4j-contrib/neo4j-streams/issues">please raise a GitHub issue</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
