<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dev Agrawal</title>
    <description>The latest articles on DEV Community by Dev Agrawal (@devagr).</description>
    <link>https://dev.to/devagr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F110967%2F95c8bffc-dfa1-4424-a33d-f6c621af87b5.jpg</url>
      <title>DEV Community: Dev Agrawal</title>
      <link>https://dev.to/devagr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devagr"/>
    <language>en</language>
    <item>
      <title>Async Transformations in Reactivity</title>
      <dc:creator>Dev Agrawal</dc:creator>
      <pubDate>Thu, 15 May 2025 05:28:49 +0000</pubDate>
      <link>https://dev.to/devagr/async-transformations-in-reactivity-9b0</link>
      <guid>https://dev.to/devagr/async-transformations-in-reactivity-9b0</guid>
      <description>&lt;p&gt;&lt;em&gt;Bringing structure to async events — lifecycles, retries, and transactions without losing composability&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is part of the &lt;strong&gt;Transformations in Reactivity&lt;/strong&gt; series.&lt;br&gt;&lt;br&gt;
In Part 1, we focused on composability. In Part 2, we introduced structured scheduling.&lt;br&gt;&lt;br&gt;
In this chapter, we tackle &lt;strong&gt;async coordination&lt;/strong&gt;: giving events the same structured lifecycle semantics Solid’s signals gained with &lt;code&gt;createAsync&lt;/code&gt; — without losing the essence of push-based flows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The Async Gap: Signals vs Events
&lt;/h2&gt;

&lt;p&gt;Signals model &lt;strong&gt;current state&lt;/strong&gt;. They’re synchronously derived and perfectly suited for reflecting application state in the UI. But when you introduce async data (like fetches), signals had to evolve mechanisms to handle suspension, pending states, and errors structurally.&lt;/p&gt;

&lt;p&gt;Solid 2.0’s &lt;code&gt;createAsync&lt;/code&gt; solved this with a structural approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signals throw during unresolved state (Suspense).&lt;/li&gt;
&lt;li&gt;Errors propagate up naturally.&lt;/li&gt;
&lt;li&gt;Async dependencies remain composable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events, however, are inherently &lt;strong&gt;push-based&lt;/strong&gt; and asynchronous. They represent &lt;strong&gt;mutations, interactions, and side effects&lt;/strong&gt;. Yet, they lack the structured semantics signals now enjoy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No built-in way to track pending state.&lt;/li&gt;
&lt;li&gt;No structured error handling.&lt;/li&gt;
&lt;li&gt;No notion of scoped retries or isolation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If signals got structured async reactivity, why not events?&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Structured Async for Events Means
&lt;/h2&gt;

&lt;p&gt;In stream-centric systems like RxJS, streams offer &lt;code&gt;next()&lt;/code&gt;, &lt;code&gt;error()&lt;/code&gt;, and &lt;code&gt;complete()&lt;/code&gt;. Useful, but flat. They don’t express:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pending/loading state.&lt;/li&gt;
&lt;li&gt;Scoped retries.&lt;/li&gt;
&lt;li&gt;Predictable execution order across async flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We need a structural lifecycle for events, much like signals have.&lt;/p&gt;

&lt;p&gt;The key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wait()&lt;/code&gt;: Emitted when async work starts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;next()&lt;/code&gt;: Emitted for resolved values (once or many times).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;error()&lt;/code&gt;: Propagates failures structurally.&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;complete()&lt;/code&gt;: Events are tied to ownership, not upstream closing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns fire-and-forget events into structured flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;createAsyncEvent&lt;/code&gt;: Declarative Async in the Event Graph
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;createAsyncEvent&lt;/code&gt; models async operations structurally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single result:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onValidated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAsyncEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validateForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Emits &lt;code&gt;wait()&lt;/code&gt; at start.&lt;/li&gt;
&lt;li&gt;Emits &lt;code&gt;next(result)&lt;/code&gt; when resolved.&lt;/li&gt;
&lt;li&gt;Emits &lt;code&gt;error(err)&lt;/code&gt; on failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Multiple results with AsyncIterable:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAsyncEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Emits multiple &lt;code&gt;next()&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;No imperative observer API. Just return values.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. What About switchMap, concatMap, mergeMap, exhaustMap?
&lt;/h2&gt;

&lt;p&gt;In RxJS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;switchMap&lt;/code&gt; cancels previous.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;concatMap&lt;/code&gt; queues.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mergeMap&lt;/code&gt; runs all concurrently.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exhaustMap&lt;/code&gt; ignores overlaps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;events&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We care about &lt;strong&gt;mutations&lt;/strong&gt;, not derived state.&lt;/li&gt;
&lt;li&gt;Every mutation should flow through.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;merge behavior&lt;/strong&gt; is default.&lt;/li&gt;
&lt;li&gt;All handlers run concurrently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;exhaust&lt;/code&gt;, and &lt;code&gt;switch&lt;/code&gt; can be built in userland:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queueing.&lt;/li&gt;
&lt;li&gt;Busy flags.&lt;/li&gt;
&lt;li&gt;Disposing previous subscriptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signals handle derived state with switchMap-like patterns.&lt;br&gt;&lt;br&gt;
Events handle side effects — and merge fits naturally.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Observing Async Events Structurally
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;createSyncEvent&lt;/code&gt;: Promises from Events
&lt;/h3&gt;

&lt;p&gt;Transforms &lt;code&gt;Handler&amp;lt;T&amp;gt;&lt;/code&gt; into &lt;code&gt;Handler&amp;lt;Promise&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onSubmitPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSyncEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onValidated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;createListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onSubmitPromise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Validated ID:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every emission creates a new promise reflecting &lt;code&gt;wait&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;createSyncStream&lt;/code&gt;: AsyncIterables from Events
&lt;/h3&gt;

&lt;p&gt;For multiple emissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onProgressStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSyncStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;createListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onProgressStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Progress:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Each trigger yields an AsyncIterable.&lt;/li&gt;
&lt;li&gt;Errors throw during iteration.&lt;/li&gt;
&lt;li&gt;Declarative consumption of event-driven streams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Scoped Retry with Event Context
&lt;/h2&gt;

&lt;p&gt;Signals retry by re-accessing. Events propagate downstream.&lt;br&gt;&lt;br&gt;
To enable retries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Async handlers register &lt;code&gt;retry()&lt;/code&gt; into the event context.&lt;/li&gt;
&lt;li&gt;Downstream listeners can access:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;createListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onValidated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getEventContext&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows isolated, structural retries without global event re-emission.&lt;/p&gt;

&lt;p&gt;Event contexts are cloned and propagated across async boundaries, maintaining isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Transactions: Deferred Work with &lt;code&gt;runTransaction&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;runTransaction&lt;/code&gt; wraps an event flow so that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wait()&lt;/code&gt; and &lt;code&gt;next()&lt;/code&gt; are deferred until the transaction completes.&lt;/li&gt;
&lt;li&gt;Errors are surfaced via the returned promise.&lt;/li&gt;
&lt;li&gt;Side effects batch after completion.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runTransaction&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;emitSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;emitOtherAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike global transitions in React/Solid, each transaction here is fully scoped and supports concurrency.&lt;/p&gt;

&lt;p&gt;For full UI transactional updates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;runTransaction&lt;/code&gt; for event flows.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;startTransition&lt;/code&gt; for signal state forks.&lt;/li&gt;
&lt;li&gt;Compose both for predictable isolation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Events as First-Class Async Structures
&lt;/h2&gt;

&lt;p&gt;By giving events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured lifecycles (&lt;code&gt;wait&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Scoped retries&lt;/li&gt;
&lt;li&gt;Transactional isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We reach parity with signals in composability and predictability — while keeping the push-based essence.&lt;/p&gt;

&lt;p&gt;The API remains minimal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return a Promise or AsyncIterable.&lt;/li&gt;
&lt;li&gt;Observe flows declaratively.&lt;/li&gt;
&lt;li&gt;No boilerplate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lets events fully participate in fine-grained, async-reactive UI architectures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next:
&lt;/h2&gt;

&lt;p&gt;Granular propagation.&lt;br&gt;&lt;br&gt;
Not every listener needs every event.&lt;br&gt;&lt;br&gt;
Next chapter explores &lt;strong&gt;Topics&lt;/strong&gt; — bringing fine-grained updates to event graphs, like stores and projections did for signals.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Scheduling Transformations in Reactivity</title>
      <dc:creator>Dev Agrawal</dc:creator>
      <pubDate>Sat, 03 May 2025 19:32:48 +0000</pubDate>
      <link>https://dev.to/devagr/scheduling-transformations-in-reactivity-da</link>
      <guid>https://dev.to/devagr/scheduling-transformations-in-reactivity-da</guid>
      <description>&lt;p&gt;&lt;em&gt;From pure event graphs to safely mutating state&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Introduction: The Real-World Punch
&lt;/h3&gt;

&lt;p&gt;There’s a saying:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Every plan is perfect until you get punched in the face.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In software, that punch often comes the moment your pure logic has to interact with the real world.&lt;/p&gt;

&lt;p&gt;In our last post, we built a clean, composable model for events — one based on transformations, ownership, and simplicity. But so far, everything has been pure. We transform values, we log them, we react — but we haven’t changed anything.&lt;/p&gt;

&lt;p&gt;That changes now.&lt;/p&gt;

&lt;p&gt;Let’s take this innocent-looking example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emitEvent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="nf"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should this log?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"hello"&lt;/code&gt; and then &lt;code&gt;"world"&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"world"&lt;/code&gt; twice?&lt;/li&gt;
&lt;li&gt;Or &lt;code&gt;"hello"&lt;/code&gt; twice?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer depends entirely on &lt;strong&gt;execution order&lt;/strong&gt;. And without clear rules, even small graphs like this become hard to reason about.&lt;/p&gt;

&lt;p&gt;Push-based systems often run into this:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;If you mutate state mid-propagation, every downstream listener sees a different world.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So now we ask: how do we bring &lt;strong&gt;consistency and predictability&lt;/strong&gt; to a reactive event system — even when it mutates?&lt;/p&gt;

&lt;p&gt;The answer lies in something we borrowed from signals: &lt;strong&gt;phased execution&lt;/strong&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Mutation Hazards in Push-Based Systems
&lt;/h3&gt;

&lt;p&gt;Push-based reactivity doesn’t have phases. It has flow.&lt;/p&gt;

&lt;p&gt;As soon as you emit a value, every subscriber runs immediately — in the order they were defined. That’s fine when you’re just transforming data. But once you introduce &lt;strong&gt;mutations&lt;/strong&gt;, the cracks start to show.&lt;/p&gt;

&lt;p&gt;Take this setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="nf"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on how these handlers were registered, and in what order they run, the logs might print:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"hello"&lt;/code&gt;, then &lt;code&gt;"world"&lt;/code&gt; — which seems logical&lt;/li&gt;
&lt;li&gt;or worse: &lt;code&gt;"world"&lt;/code&gt; twice — because the state was changed mid-propagation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s no real “before and after” in this model. Everything is firing during the same tick, and nothing is scheduled — so side effects collide with each other, and &lt;strong&gt;reads become unpredictable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Push-based graphs become fragile when reads and writes happen in the same pass.&lt;/p&gt;

&lt;p&gt;If we want predictable behavior — if we want to reason about event graphs like pure functions — we need to &lt;strong&gt;separate mutation from computation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Just like signals did.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Lessons from Signals — Phased Execution
&lt;/h3&gt;

&lt;p&gt;Signals hit the same wall events do: mixing computation and side effects creates unpredictable behavior.&lt;/p&gt;

&lt;p&gt;Solid’s answer was &lt;strong&gt;phased execution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In Solid 2.0, reactivity happens in &lt;strong&gt;layers&lt;/strong&gt;, not all at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pure derivations&lt;/strong&gt; (memos, derived signals)  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These run first and synchronously during a synthetic "clock cycle".&lt;/li&gt;
&lt;li&gt;No side effects. Just computation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Side effects&lt;/strong&gt; (&lt;code&gt;createEffect&lt;/code&gt;)  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These are deferred until after the graph stabilizes.&lt;/li&gt;
&lt;li&gt;Used to read from the DOM, notify external systems, or manage local resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;“All changes to the DOM or outside world happen after all pure computations are complete.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even though some reactive work runs immediately, Solid 2.0 internally queues it and flushes the graph once it’s fully consistent. This guarantees that &lt;strong&gt;the entire reactive graph stabilizes in a single clock cycle&lt;/strong&gt;, and only then do effects run.&lt;/p&gt;

&lt;p&gt;It’s a small shift, but a powerful one:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Separate pure logic from side effects, and you get predictability.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s what we want for events, too.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. Bringing Phases to Events
&lt;/h3&gt;

&lt;p&gt;If signals needed phased execution to stay predictable, &lt;strong&gt;events need the same&lt;/strong&gt; — maybe even more.&lt;/p&gt;

&lt;p&gt;Here’s the structure we adopt for event execution:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Phase 1: Pure event graph&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;All event handlers (transformers) run immediately.&lt;/li&gt;
&lt;li&gt;No state changes, no side effects — just pure mapping.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Phase 2: Mutations&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Instead of calling &lt;code&gt;setState()&lt;/code&gt; directly inside handlers, you &lt;strong&gt;schedule it&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You use &lt;code&gt;createMutation(handler, effectFn)&lt;/code&gt; — the handler runs in phase 1, and &lt;code&gt;effectFn&lt;/code&gt; is deferred to phase 2.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Phase 3+: Signals and UI&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;After mutations, the rest of Solid’s reactive system proceeds as usual.&lt;/li&gt;
&lt;li&gt;Signals notify, DOM updates happen, effects fire — all downstream of a clean update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lets you build composable flows like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onNext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;createMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onClick&lt;/code&gt; runs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onNext&lt;/code&gt; transforms the value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setCount(...)&lt;/code&gt; is &lt;strong&gt;queued&lt;/strong&gt;, not run immediately&lt;/li&gt;
&lt;li&gt;The UI updates &lt;strong&gt;after&lt;/strong&gt; all pure logic completes&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Blocking Unsafe Patterns
&lt;/h3&gt;

&lt;p&gt;For phased execution to work, we need &lt;strong&gt;clear boundaries&lt;/strong&gt; — but we don’t want to over-restrict expressive patterns either.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;State mutations (&lt;code&gt;setState&lt;/code&gt;) must never happen in phase 1.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If &lt;code&gt;setState()&lt;/code&gt; happens during the pure event phase, downstream handlers may see a changed world — and that breaks everything.&lt;/p&gt;

&lt;p&gt;To enforce this, any &lt;code&gt;setState()&lt;/code&gt; inside a pure event handler should either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Throw a dev-time error, or&lt;/li&gt;
&lt;li&gt;Be automatically scheduled as a mutation
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ not allowed directly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about &lt;code&gt;emit()&lt;/code&gt;? Emitting from inside a handler is &lt;strong&gt;pure&lt;/strong&gt; — it just expands the graph.&lt;br&gt;&lt;br&gt;
Still, it should be used thoughtfully. Overusing nested emits can make graphs harder to trace.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mutation breaks the phase model.&lt;br&gt;&lt;br&gt;
Emission just grows the graph.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  6. &lt;code&gt;createMutation&lt;/code&gt; in Practice
&lt;/h3&gt;

&lt;p&gt;This is your gateway to phase 2 — safe, scheduled mutation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onNext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;createMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to perform multiple updates?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also inject logging, analytics, or cleanups — all scoped, all deferred.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Sync Parity with Signals
&lt;/h3&gt;

&lt;p&gt;Solid 2.0 uses &lt;strong&gt;auto-batching&lt;/strong&gt; — effects run only after the graph stabilizes.&lt;br&gt;&lt;br&gt;
To match this behavior, we flush all queued mutations automatically after every &lt;code&gt;emit()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;emitIncrement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// queues the mutation&lt;/span&gt;
&lt;span class="c1"&gt;// mutation is flushed right after emit completes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need to group multiple emits?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;batchEvents&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;emitAdd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;emitSubtract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The queue flushes after the batch — keeping mutation and UI updates consistent with signal behavior.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Signals (2.0)&lt;/th&gt;
&lt;th&gt;Events&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;Auto-batched&lt;/td&gt;
&lt;td&gt;Auto-flushed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual control&lt;/td&gt;
&lt;td&gt;&lt;code&gt;flushSync()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;batchEvents()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  8. Final Example — Predictable Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emitIncrement&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onNext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;createMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;emitIncrement()&lt;/code&gt; fires.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onIncrement&lt;/code&gt; computes &lt;code&gt;count() + 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setCount(...)&lt;/code&gt; is queued.&lt;/li&gt;
&lt;li&gt;The mutation is flushed after the event cycle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Want logging?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Next count:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nf"&gt;createMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to batch updates?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;batchEvents&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;emitSave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;emitClear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pure. Predictable. Phased.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Next time: async, errors, retries, and full integration with suspense.&lt;/p&gt;

</description>
      <category>signals</category>
      <category>observables</category>
      <category>reactivity</category>
      <category>solidjs</category>
    </item>
    <item>
      <title>Transformations in Reactivity</title>
      <dc:creator>Dev Agrawal</dc:creator>
      <pubDate>Sat, 03 May 2025 18:28:00 +0000</pubDate>
      <link>https://dev.to/devagr/transformations-in-reactivity-1nn5</link>
      <guid>https://dev.to/devagr/transformations-in-reactivity-1nn5</guid>
      <description>&lt;p&gt;&lt;em&gt;What happens when we stop treating events like second-class signals?&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Introduction
&lt;/h3&gt;

&lt;p&gt;Signals have gone through a quiet renaissance.&lt;/p&gt;

&lt;p&gt;Once a niche concept, they’ve become the backbone of fine-grained reactivity across modern frameworks — from SolidJS to Svelte 5, Vue’s Composition API, and beyond. With laziness, ownership, batching, and async support, signals are no longer just state — they’re a reactive architecture.&lt;/p&gt;

&lt;p&gt;Meanwhile, events have stayed… familiar.&lt;br&gt;&lt;br&gt;
Push-based systems like RxJS still dominate, with powerful abstractions — and plenty of baggage. They’re expressive, but complex. Powerful, but leaky.&lt;/p&gt;

&lt;p&gt;This series explores a different direction:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;What if we evolved events the way signals evolved?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
What if push and pull reactivity didn’t require different paradigms, just different shapes?&lt;/p&gt;

&lt;p&gt;Let’s start small — by rebuilding events from first principles.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. From Observables to Event Graphs
&lt;/h3&gt;

&lt;p&gt;RxJS gave us a model: streams of values transformed by declarative operators.&lt;br&gt;&lt;br&gt;
It works. But it comes with weight.&lt;/p&gt;

&lt;p&gt;Even trivial flows — say, responding to a button click and transforming a value — require a pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then comes teardown logic, lifecycle handling, and the occasional “why didn’t this emit?” debugging session.&lt;/p&gt;

&lt;p&gt;The idea behind this model isn’t to replace RxJS — it’s to explore what happens when you &lt;strong&gt;stop thinking in streams&lt;/strong&gt; and start thinking in &lt;strong&gt;reactive functions&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Instead of operators and pipelines, what if you just… mapped?&lt;/p&gt;


&lt;h3&gt;
  
  
  3. Solid as Inspiration
&lt;/h3&gt;

&lt;p&gt;SolidJS didn’t invent signals — &lt;strong&gt;S.js&lt;/strong&gt; did.&lt;br&gt;&lt;br&gt;
But Solid made them practical, scalable, and fast. It took a pull-based reactive core and layered on &lt;strong&gt;ownership&lt;/strong&gt;, &lt;strong&gt;lifecycles&lt;/strong&gt;, and &lt;strong&gt;structured scheduling&lt;/strong&gt; — turning signals into a system.&lt;/p&gt;

&lt;p&gt;In Solid, signals live inside a graph that disposes itself. When a scope ends — like a component unmounting — all reactive logic inside it is automatically cleaned up. No &lt;code&gt;unsubscribe()&lt;/code&gt;. No memory leaks. No manual lifecycle wiring.&lt;/p&gt;

&lt;p&gt;That’s the leap: &lt;strong&gt;reactivity that lives and dies with its context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This model works for signals. What happens if we apply the same ideas to events?&lt;/p&gt;


&lt;h3&gt;
  
  
  4. A New Default: Event Handlers as Transformers
&lt;/h3&gt;

&lt;p&gt;Traditionally, push-based systems treat streams like pipelines. You create a source, then pass it through a chain of operators to get what you want.&lt;/p&gt;

&lt;p&gt;But what if you didn’t need a chain?&lt;/p&gt;

&lt;p&gt;What if the default behavior of an event was simply: &lt;strong&gt;transform a value and hand it off&lt;/strong&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like that, you've turned one event into another. No &lt;code&gt;.pipe()&lt;/code&gt;, no &lt;code&gt;.map()&lt;/code&gt;, no imported operators — just &lt;strong&gt;function composition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This model simplifies everything. Here’s how you might build a derived counter from a basic event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emitIncrement&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onNextCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;onNextCount&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Next count will be:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’ve used signals before, this should feel instantly familiar. It’s not a stream — it’s a value transformer, scoped to a reactive lifecycle.&lt;/p&gt;

&lt;p&gt;You can compose multiple steps, too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onEven&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onNextCount&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;haltEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nf"&gt;onEven&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Even count:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each handler returns a new transformed event. That’s it.&lt;br&gt;&lt;br&gt;
No operator chain. No nested observables. No cleanup logic.&lt;/p&gt;

&lt;p&gt;And yes, you can still pass values through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Clicked at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. Filtering with &lt;code&gt;haltEvent()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;So if we’ve dropped &lt;code&gt;filter&lt;/code&gt;, how do we stop an event from propagating?&lt;/p&gt;

&lt;p&gt;With one helper: &lt;code&gt;haltEvent()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nf"&gt;haltEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering becomes a conditional — not an operator. The behavior is local, readable, and imperative.&lt;br&gt;&lt;br&gt;
And that’s intentional. The more we reduce the operator surface, the more intuitive the graph becomes.&lt;/p&gt;

&lt;p&gt;This approach isn’t about losing power — it’s about making the &lt;strong&gt;simple path&lt;/strong&gt; the default.&lt;/p&gt;


&lt;h3&gt;
  
  
  6. Memory &amp;amp; Lifecycle
&lt;/h3&gt;

&lt;p&gt;Push-based systems historically struggle with memory management.&lt;/p&gt;

&lt;p&gt;You subscribe, and then… you unsubscribe. Or you forget. Or you build a helper. Or you use &lt;code&gt;takeUntil&lt;/code&gt;. Or you write a &lt;code&gt;useObservable()&lt;/code&gt; hook that does the teardown for you.&lt;/p&gt;

&lt;p&gt;In RxJS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;onDestroy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or maybe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;takeUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destroy$&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even internally, you return a cleanup function from your subscription logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an ownership model, that all disappears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;onCleanup&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No teardown function needed.&lt;br&gt;&lt;br&gt;
No &lt;code&gt;unsubscribe()&lt;/code&gt; anywhere.&lt;br&gt;&lt;br&gt;
The event lives as long as the scope that created it.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;reactivity with lifecycle built in&lt;/strong&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  7. Rethinking Completion
&lt;/h3&gt;

&lt;p&gt;Observables give you &lt;code&gt;.complete()&lt;/code&gt; as a way to say, “I’m done.” It’s a way for the stream to manage its own lifecycle.&lt;/p&gt;

&lt;p&gt;But in UI systems, streams rarely complete.&lt;br&gt;&lt;br&gt;
A click handler doesn’t complete. A keypress doesn’t complete. An internal state change doesn’t complete.&lt;/p&gt;

&lt;p&gt;Instead of letting streams declare themselves done, we flip the model:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;streams live as long as they’re used.&lt;/strong&gt; And they stop when their owner stops.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emitClick&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No manual teardown. No lifecycle ceremony.&lt;br&gt;&lt;br&gt;
When the scope disposes, so does the handler.&lt;/p&gt;

&lt;p&gt;This also means events don’t need to be recreated to be reused.&lt;br&gt;&lt;br&gt;
No reinitialization. No cascading unsubscribes. Just &lt;strong&gt;permanent event nodes&lt;/strong&gt; that can be reconnected at will.&lt;/p&gt;


&lt;h3&gt;
  
  
  8. Higher-Order Events (Sync Only)
&lt;/h3&gt;

&lt;p&gt;Yes — you can build &lt;code&gt;switchMap&lt;/code&gt;. But you don’t need to.&lt;/p&gt;

&lt;p&gt;An event can just emit another event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;higherEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="nf"&gt;higherEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innerEvent&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;innerEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inner event value:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each time &lt;code&gt;higherEvent&lt;/code&gt; emits, it provides a new &lt;code&gt;innerEvent&lt;/code&gt;. You subscribe to it. You decide what to do with the old one.&lt;/p&gt;

&lt;p&gt;Want a helper?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createDynamicEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you’ve got a flattened stream of inner values. And you didn’t write a single operator.&lt;/p&gt;

&lt;p&gt;This works great for synchronous graphs.&lt;br&gt;&lt;br&gt;
Async adds more complexity — and we’ll get there. Just not today.&lt;/p&gt;


&lt;h3&gt;
  
  
  9. Final Transformation Example
&lt;/h3&gt;

&lt;p&gt;Here’s the full pattern — from event to transformation to subscriber:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emitIncrement&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Next count will be:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ve declared an event, transformed it, and used it — all in a few lines.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No operators.
&lt;/li&gt;
&lt;li&gt;No unsubscribe.
&lt;/li&gt;
&lt;li&gt;No cleanup code.
&lt;/li&gt;
&lt;li&gt;Just pure transformation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the baseline:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Events as composable, scoped, reactive functions.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
And it’s only the beginning.&lt;/p&gt;




&lt;h3&gt;
  
  
  Next Up: Scheduling
&lt;/h3&gt;

&lt;p&gt;What happens when events start mutating state?&lt;br&gt;&lt;br&gt;
What happens when the same event graph reads and writes at the same time?&lt;/p&gt;

&lt;p&gt;That’s where scheduling comes in.&lt;br&gt;&lt;br&gt;
And in the next post, we’ll show how phase-based execution solves the hardest problem in event reactivity: &lt;strong&gt;ordering&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>signals</category>
      <category>solidjs</category>
      <category>observables</category>
      <category>rxjs</category>
    </item>
    <item>
      <title>Dynamic Typing is Obsolete</title>
      <dc:creator>Dev Agrawal</dc:creator>
      <pubDate>Wed, 13 Dec 2023 06:59:33 +0000</pubDate>
      <link>https://dev.to/devagr/dynamic-typing-is-obsolete-5404</link>
      <guid>https://dev.to/devagr/dynamic-typing-is-obsolete-5404</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Dynamically typed languages are better than statically typed languages&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This statement used to be true for various reasons. It’s not anymore. Let’s dig into why.&lt;/p&gt;

&lt;p&gt;Dynamically typed languages and frameworks like Ruby on Rails and LAMP stack blew up in popularity for various reasons. Let’s rewind the clock back a couple decades or so and look at the context.&lt;/p&gt;

&lt;p&gt;Software development was dominated by statically typed languages like C++, Java, and C#.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;These languages and frameworks had a big learning curve&lt;/li&gt;
&lt;li&gt; Code was very verbose because of the ceremony of type definitions&lt;/li&gt;
&lt;li&gt;Required expensive specialized tooling that shipped in physical CDs&lt;/li&gt;
&lt;li&gt;IDEs were very slow and buggy, especially on older hardware&lt;/li&gt;
&lt;li&gt;Feedback loop was slow because of long compilation times&lt;/li&gt;
&lt;li&gt;Ecosystems were primarily stewarded by corporations and proprietary software&lt;/li&gt;
&lt;li&gt;Frameworks were primarily intended for desktop apps, web apps were an afterthought&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ruby on Rails and LAMP stack were positioned perfectly to sweep and dominate the ecosystem at this point.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Languages like Ruby and PHP had a much smaller learning curve&lt;/li&gt;
&lt;li&gt;Dynamic nature and syntactic sugar made it quick and fun to program&lt;/li&gt;
&lt;li&gt;No specialized tooling was required, fire up notepad and start building your app&lt;/li&gt;
&lt;li&gt;No slow and buggy IDEs, you could literally REPL your code into the terminal&lt;/li&gt;
&lt;li&gt;No compile step means faster feedback loops, make a change, hit save, refresh the browser&lt;/li&gt;
&lt;li&gt;Ecosystems were extremely open source and community driven&lt;/li&gt;
&lt;li&gt;The sole purpose was enabling anyone to make web apps, instead of adding web capabilities to an existing stack&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I obviously haven’t been around that long, but those who have remember static typing not as it is today, but as it was back then. It was an indicator of ceremony, slow feedback loops, and proprietary dinosaurs.&lt;/p&gt;

&lt;p&gt;Static typing has made a major comeback over the last decade, and almost everything that turned people away from them and towards the comfort of PHP and Ruby has been UNO reversed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modern static languages like Typescript have ridiculously low learning curves&lt;/li&gt;
&lt;li&gt;Type inference takes out 95% of the ceremony and terse syntax&lt;/li&gt;
&lt;li&gt;The most powerful tools also happen to be open source and very well documented&lt;/li&gt;
&lt;li&gt;Advancements in hardware and optimization techniques have made IDEs extremely snappy&lt;/li&gt;
&lt;li&gt;Compilations are ridiculously quick, especially if the compiler knows the data types beforehand&lt;/li&gt;
&lt;li&gt;Even the big bad Microsoft has completely turned around and committed to open source&lt;/li&gt;
&lt;li&gt;Typescript is not the only static language with an ecosystem primarily geared towards web development&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The factors that blew up Rails and LAMP in popularity are now the same factors that favor ecosystems that offer complete type safety. Dynamic typing has become, to put it strongly, obsolete. They are artifacts of the past.&lt;/p&gt;

&lt;p&gt;Yes you can use dynamically typed languages to build real systems for scale. But you will pay the cost of maintenance, performance, and scalability, and eventually will have to rebuild using infrastructure that offers much better guarantees.&lt;/p&gt;

&lt;p&gt;Dynamically typed languages are used not because they are dynamically typed, but because they have specific technical benefits (e.g. Elixir for building distributed systems) or ecosystem benefits (e.g. Python for data science).&lt;/p&gt;

&lt;p&gt;Starting off with type-safe languages will give you faster feedback loops, more helpful IDE tools, more freedom to make changes without breaking stuff, and better collaboration through internal contracts and documentation at a very low cost.&lt;/p&gt;

&lt;p&gt;This article is essentially a summary of this talk &lt;a href="https://youtu.be/Tml94je2edk?si=KCLolsf7Iggihie"&gt;"Why Static Typing Came Back” by Richard Feldman&lt;/a&gt;. Go watch the full talk for an even bigger history lesson and so many more arguments that I couldn’t even mention here.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>typescript</category>
      <category>dynamictyping</category>
      <category>statictyping</category>
    </item>
    <item>
      <title>Thoughts on Server Components + Websockets</title>
      <dc:creator>Dev Agrawal</dc:creator>
      <pubDate>Wed, 13 Dec 2023 06:15:58 +0000</pubDate>
      <link>https://dev.to/devagr/thoughts-on-server-components-websockets-316n</link>
      <guid>https://dev.to/devagr/thoughts-on-server-components-websockets-316n</guid>
      <description>&lt;h1&gt;
  
  
  Thoughts on Server Components + Websockets
&lt;/h1&gt;

&lt;p&gt;I've been thinking about this for way too long now, and right now I am convinced that this is potentially an amazing way to build realtime applications. Would love to be proven wrong.&lt;br&gt;
Currently I believe there are 2 ways to achieve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just reload the page
&lt;/h2&gt;

&lt;p&gt;This is what &lt;a href="https://twitter.com/devagrawal09/status/1731095782541943161"&gt;I have working so far&lt;/a&gt;. The component listens on a live topic, and when it receives a message it just reloads the page, i.e., load all the server data again.&lt;/p&gt;

&lt;p&gt;This is the most bare and simple integration, but also very powerful, because you get security by default. It's like the Stripe integration - server creates a secure session, then tells the client how to connect to it. If the server component is not rendered, the client has no way to connect to a topic and receive unauthorized messages.&lt;/p&gt;

&lt;p&gt;This is also not the most efficient - websockets are being used purely as a notification, not to transfer data. Every realtime update is causing a server fetch.&lt;/p&gt;

&lt;p&gt;But a huge class of applications can greatly benefit from a very simple drop in "make this page realtime" solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  RSC over WebSockets
&lt;/h2&gt;

&lt;p&gt;Getting tricker now. This is essentially an entirely different server that is also running Server Components, but with state! This is VERY different from how we use Server Components today which are designed to run in stateless environments.&lt;/p&gt;

&lt;p&gt;The DX will be very similar - a server component will return JSX that will be sent to the client as a websocket message where its handled by React. Except there's no navigation - the client just establishes a connection and shows a Suspense fallback, and the server decides when it needs to render the UI.&lt;/p&gt;

&lt;p&gt;So how do these components get triggered? Well you can just listen to websocket messages, or have another http api on the side, or subscribe to rabbitmq, or connect to a cloud service.&lt;/p&gt;

&lt;p&gt;These server components will require an entirely new concept of Routing defined on top of WebSockets instead of HTTP, and the layer of infrastructure to provide that (like Next.js).&lt;/p&gt;

&lt;p&gt;They're also have to be detached from Next.js - the websocket server component has to be mounted on the Client instead of on the Server, because Next.js's routing cannot interfere with it.&lt;br&gt;
(Obviously SSR is a non-issue with websockets)&lt;/p&gt;

&lt;p&gt;I don't think there's any solutions to do this today, but if someone is interested in building one let me know. This is kindof a big problem because you need to build a metaframework but for websockets, but you also don't have to deal with SSR so it's slightly easier I guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Serverless?
&lt;/h2&gt;

&lt;p&gt;Great question.&lt;br&gt;
My implementation of #1 runs on AWS and uses a combination of API Gateway and DynamoDB. "state" on the server doesn't have to be in memory, it can be a database, especially if it's a low latency KV, or even better, a durable object. And the websocket part can use any underlying infrastructure.&lt;br&gt;
It's the same for #2 - stateful RSCs can easily exist on serverless runtimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  But Why RSCs with WebSockets
&lt;/h2&gt;

&lt;p&gt;The DX of Stateful RSCs is basically the exact thing you imagined when you first heard "server components". It's components that can useState and setState on the server.&lt;/p&gt;

&lt;p&gt;This is a very powerful way to build interactive apps, because similar to how RSCs encapsulate the entire http layer, state RSCs can encapsulate the entire websocket layer and give you complete control over how much work is done on the server vs client, and directly connect your UI to server events. Basically if the underlying implementation completely changed from websockets to something else we wouldn't even notice.&lt;/p&gt;

&lt;p&gt;Let me know if I'm crazy or is this real.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>websockets</category>
      <category>reactservercomponents</category>
    </item>
    <item>
      <title>Clerk Webhooks: Data Sync with Convex</title>
      <dc:creator>Dev Agrawal</dc:creator>
      <pubDate>Wed, 13 Dec 2023 06:08:35 +0000</pubDate>
      <link>https://dev.to/clerk/clerk-webhooks-data-sync-with-convex-1jeh</link>
      <guid>https://dev.to/clerk/clerk-webhooks-data-sync-with-convex-1jeh</guid>
      <description>&lt;p&gt;Composing an application out of multiple data sources can be challenging, and while Clerk’s APIs work great for most use cases, webhooks offer the next level of integration that enables you to take full advantage of your existing stack. This post will cover how to synchronize user data from Clerk into your own backend using Webhooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Sync with Convex
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://convex.dev/"&gt;Convex&lt;/a&gt; is a real time &lt;a href="https://en.wikipedia.org/wiki/Backend_as_a_service"&gt;Backend-as-a-Service&lt;/a&gt; Provider that makes building highly dynamic and interactive experiences with React easier than ever.&lt;/p&gt;

&lt;p&gt;Clerk offers a first-class integration with Convex. The queries and mutations living in your Convex instance can be authenticated with a token that was created by Clerk. This integration is covered in more detail &lt;a href="https://clerk.com/docs/integrations/databases/convex?utm_source=DevRel&amp;amp;utm_medium=blog&amp;amp;utm_campaign=docslanding&amp;amp;utm_content=webhooks-blog&amp;amp;utm_term=convex-post"&gt;in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Convex is also a great target for synchronizing user data from Clerk using webhooks, since you can take full advantage of Convex’s realtime capabilities for user data.&lt;/p&gt;

&lt;p&gt;If you are not sure what webhooks are and how to use webhooks with Clerk, it’s recommended to read &lt;a href="https://clerk.com/blog/webhooks-getting-started?utm_source=DevRel&amp;amp;utm_medium=blog&amp;amp;utm_campaign=blog-to-blog&amp;amp;utm_content=webhooks-blog&amp;amp;utm_term=convex-post"&gt;this getting started post&lt;/a&gt; first.&lt;/p&gt;

&lt;p&gt;To demonstrate data synchronization with Convex, we can use &lt;a href="https://github.com/thomasballinger/convex-clerk-users-table"&gt;Convex’s Clerk starter repo&lt;/a&gt;. You can clone and run this repo locally by following the instructions in the repo.&lt;/p&gt;

&lt;p&gt;This post will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explain how the demo uses data sync with reactive queries together&lt;/li&gt;
&lt;li&gt;Show code examples of the webhooks handler implementation in the demo&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Demo
&lt;/h2&gt;

&lt;p&gt;The demo application is a chat room where users can send and receive messages. To access the room, a user needs to sign up for an account using the Clerk integration. Once signed in, the user is redirected to a chat room, which establishes a reactive query to the Convex database. This reactive query looks for the user in the database, and returns a “No Clerk User” if the user is not found, and a “Logged In” if the user is found.&lt;/p&gt;

&lt;p&gt;Initially after signing up, the user will not be found in the database. Since the signup process happened with Clerk, Convex has no record of this user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0KttC3l9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9lg3kz1geikkydu9xbxr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0KttC3l9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9lg3kz1geikkydu9xbxr.png" alt="Image description" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where webhooks come into play. After the user signs up with Clerk, Clerk will send an http request to a pre-defined URL with data about the sign up. This URL will point to a Convex HTTP Action, which will parse the data and create a record in the Convex database for the user. Now the client can query for this user again and receive the expected response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ItTU0L6M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjwp0pfmvpf77siyhbeg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ItTU0L6M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjwp0pfmvpf77siyhbeg.png" alt="Image description" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But since Convex queries are reactive, the client doesn’t need to retry the query, instead it will automatically receive an update from the server with the user data. This will trigger React to re-render the UI, providing the user access to the chat room.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jvxeD0aC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gxli0i6bjmgaedbtryrl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jvxeD0aC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gxli0i6bjmgaedbtryrl.png" alt="Image description" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;To look at the webhook implementation, we can open the &lt;code&gt;convex/http.ts&lt;/code&gt; file in this repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// convex/http.ts&lt;/span&gt;

&lt;span class="c1"&gt;// define the webhook handler&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClerkWebhook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;httpAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error occured&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// define the http router&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;httpRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// define the webhook route&lt;/span&gt;
&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/clerk-users-webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handleClerkWebhook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;handleClerkWebhook&lt;/code&gt; function is responsible for… you guessed it… handling the Clerk webhook. It’s a function decorated with &lt;code&gt;httpAction&lt;/code&gt; and receives the HTTP request and a context object to access other Convex resources.&lt;/p&gt;

&lt;p&gt;This function runs security validation on the request to ensure that it came from a trusted source, and runs some database mutations based on the type of event received. The mutations simply mirror the event - creating, updating, or deleting a user in Clerk simply results in creating, updating, or deleting the same user in Convex. It forms a copy of the Clerk user table within Convex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// convex/http.ts&lt;/span&gt;

  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user.updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateOrCreateUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;clerkUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user.deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ignored Clerk webhook event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the most powerful feature of Convex - Queries, to access the user data directly, which means the application doesn’t need to make additional requests to Clerk to fetch the user data (also called the n+1 problem), and the user interface can get notified if the data changes (e.g. if the user changes their name or profile picture).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// convex/users.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;internalQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;by_clerk_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clerkUser.id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full Potential
&lt;/h2&gt;

&lt;p&gt;Webhooks are a powerful feature that enable integrations with existing backend tools, allowing you to use their technical potential to the fullest, while also using Clerk's user management capabilities to the fullest. They help glue disjointed parts of the backend together so that you can pick whatever technology suits your use case for storing and processing data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready to Integrate Authentication in Your App?
&lt;/h2&gt;

&lt;p&gt;You can explore the &lt;a href="https://clerk.com/docs/integrations/webhooks?utm_source=DevRel&amp;amp;utm_medium=blog&amp;amp;utm_campaign=docslanding&amp;amp;utm_content=webhooks-blog&amp;amp;utm_term=convex-post"&gt;Clerk Webhooks docs&lt;/a&gt; to learn more about the webhook events and data that is exposed by Clerk and build your own integrations today!&lt;/p&gt;

&lt;p&gt;For more in-depth technical inquiries or to engage with our community, feel free to &lt;a href="https://discord.com/invite/b5rXHjAg7A"&gt;join our Discord&lt;/a&gt;. Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our Twitter account, &lt;a href="https://twitter.com/ClerkDev"&gt;@ClerkDev&lt;/a&gt;. Your journey to seamless User Management starts here!&lt;/p&gt;

</description>
      <category>react</category>
      <category>auth</category>
      <category>data</category>
      <category>webhooks</category>
    </item>
    <item>
      <title>Clerk Webhooks: Getting Started</title>
      <dc:creator>Dev Agrawal</dc:creator>
      <pubDate>Wed, 29 Nov 2023 01:30:33 +0000</pubDate>
      <link>https://dev.to/clerk/clerk-webhooks-getting-started-lbi</link>
      <guid>https://dev.to/clerk/clerk-webhooks-getting-started-lbi</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fe1ql88v4%2Fproduction%2F2198f52b2bb1382d78469b0de94c962c25c0782d-4800x2520.png%3Ffit%3Dmax%26auto%3Dformat" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fe1ql88v4%2Fproduction%2F2198f52b2bb1382d78469b0de94c962c25c0782d-4800x2520.png%3Ffit%3Dmax%26auto%3Dformat" alt="Clerk Webhooks: Getting Started"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In an age of software that is composed of decoupled services built by specialized (often outsourced) teams, webhooks play an important architectural role. Webhooks enable us to outsource important user interactions and services to external platforms that are specifically designed for those purposes, and still have our applications be able to respond to those interactions.&lt;/p&gt;

&lt;p&gt;For example, Clerk is specifically designed to handle authentication and user management related interactions on behalf of your applications. Webhooks allow Clerk to notify your application of these interactions so that you can implement custom follow-up logic. These interactions include (but are not limited to) signing in, changing profile information, creating an organization, or getting invited to an organization. Clerk ensures that all these important interactions are handled securely and responsively, while giving you a way to implement follow-up logic.&lt;/p&gt;

&lt;p&gt;In this blog post we will build a simple webhook integration in a Next.js project. We will also explore the use cases of webhooks with Clerk and discuss how to best handle webhooks for production-ready applications.&lt;/p&gt;

&lt;p&gt;This post assumes basic familiarity with Next.js and Clerk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Webhooks in Next.js
&lt;/h2&gt;

&lt;p&gt;For this walkthrough we will start with a Next.js project that is already setup with Clerk. However, you can create a webhook subscription in any server-side web framework like Express or Fastify.&lt;/p&gt;

&lt;p&gt;If you don’t already have a Next.js project setup with Clerk, you can start by cloning the Clerk Next.js App Router template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/clerkinc/clerk-next-app.git&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a minimal Next.js project fully setup with Clerk’s embedded components and a secured dashboard page.&lt;/p&gt;

&lt;p&gt;Create a .env.local file in the root of the project and add your keys here from the Clerk dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_pub_key
CLERK_SECRET_KEY=your_clerk_secret_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s create an API route that will handle the Clerk webhook. This handler will live in app/api/clerk/route.ts and will have the URL of your-app.com/api/clerk.&lt;/p&gt;

&lt;p&gt;Webhooks are POST requests by default, so let’s create a handler for receiving POST requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The webhook event data is available as the request payload. Let’s parse it out and log it to the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clerk provides a WebhookEvent type for strong typescript inference. For now we will simply assert the webhook payload to be of this type.&lt;/p&gt;

&lt;p&gt;Let’s also create a simple GET handler to test if we can access this URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this endpoint as a public route to the Clerk middleware&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;publicRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/clerk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/((?!.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;..*|_next).*)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the development server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can navigate to this endpoint in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqz5kecwohm6xbxnq2tj6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqz5kecwohm6xbxnq2tj6.png" alt="Navigating to localhost:3000/api/clerk in the browser should show the JSON object with the "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s all we need to setup a simple webhook endpoint! Now we can provide Clerk with a URL to this endpoint, and we can start receiving events.&lt;/p&gt;

&lt;p&gt;However, we are not ready to test this locally yet. While the application is accessible on localhost in our machine, it’s not accessible to anything outside our local network. Since webhook requests are originated from Clerk’s servers, the localhost URL will just point back to the Clerk server instead of our machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzgqntpkncarj393f9kl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzgqntpkncarj393f9kl.png" alt="Any requests by Clerk servers to a localhost URL will loop back to themselves"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While we could provide Clerk with the public IP address of our machine, any incoming requests to our local home/work networks will be blocked by firewalls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8cs9zfi9ts8f418jfya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8cs9zfi9ts8f418jfya.png" alt="Requests by Clerk servers to a personal/work machine will be blocked by firewalls"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To work around this problem, we can use a tunneling service like localtunnel, which creates a secure tunnel to our local machine from the cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9if5i5fvml79o7otv4bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9if5i5fvml79o7otv4bs.png" alt="Tunneling services allow Clerk servers to send requests to local servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s setup localtunnel in a new terminal window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; localtunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the installation is complete, we can create a tunnel to our local server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lt &lt;span class="nt"&gt;--port&lt;/span&gt; 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an output in your terminal like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c0uds3nvahmn11q8u85.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c0uds3nvahmn11q8u85.png" alt="Starting a localtunnel session in the terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Localtunnel will generate a random URL on the loca.lt domain that will point to your locally running server on port 3000. We can navigate to this new URL in our browser.&lt;/p&gt;

&lt;p&gt;Localtunnel has a security mechanism built in to prevent abuse from malicious actors and phishing links. It asks for the public IP address of your local machine to ensure that it’s really you trying to access your application. Follow the instructions on this page to get your public IP address, and submit it on this page.&lt;/p&gt;

&lt;p&gt;Once that’s done, we can access our application on this URL. We should also be able to access our webhook endpoint.&lt;/p&gt;

&lt;p&gt;If the application doesn’t show up, make sure both the nextjs server and localtunnel are running on separate terminal windows.&lt;/p&gt;

&lt;p&gt;Now we are ready to plug our endpoint into Clerk’s dashboard. Go to the Webhooks page, and click Add Endpoint. Enter the localtunnel URL here. You can also add a description for this endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feufzl0n9or86sfas2stp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feufzl0n9or86sfas2stp.png" alt="Creating a webhook endpoint in the Clerk dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can select which specific events you want to receive at this endpoint. Let’s leave these unselected for now so that we can receive webhooks for all events. Hit Create.&lt;/p&gt;

&lt;p&gt;Once the webhook endpoint is created, we can navigate to the Testing tab. Here we can trigger an example event manually to test our webhook endpoint. Select any event from the dropdown, and hit Send Example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5nfd345zbyfldw6ib78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5nfd345zbyfldw6ib78.png" alt="Sending a test webhook event"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your application is running locally and tunneling is set up correctly, you should see a message in your console with the example event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oemskzyil22osq1isg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oemskzyil22osq1isg4.png" alt="The test webhook event showing up in the terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will also see a log entry in the Testing tab that shows the status of the webhook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlt9g8skgxeljlh6fp1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlt9g8skgxeljlh6fp1y.png" alt="Webhook delivery shown as failed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though the webhook was correctly received and logged to the console, the dashboard marks the webhook delivery as failed. To ensure that the delivery is marked as succeeded, we need to return a success response from our endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s send another example event. This time it the webhook should be marked as succeeded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tzmbjzvlapslap0bif6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tzmbjzvlapslap0bif6.png" alt="Webhook delivery shown as successful"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can test the webhook with real events. Navigate to your application and sign up with a new account. This should log two webhook events in the console - a user.created and a session.created event.&lt;/p&gt;

&lt;p&gt;Congratulations, we now have a functioning webhook receiver in our application! We can now further explore what events are triggered by Clerk. For example,&lt;/p&gt;

&lt;p&gt;Updating the user profile sends a user.updated event&lt;/p&gt;

&lt;p&gt;Signing out of the application sends a session.ended event&lt;/p&gt;

&lt;p&gt;Remotely signing out a different device will result in a session.revoked event&lt;/p&gt;

&lt;p&gt;Creating a new organization will trigger an organization.created and an organizationMembership.created event&lt;/p&gt;

&lt;p&gt;We can also see logs for real webhook events in the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1bgdu618uimzrkysfg0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1bgdu618uimzrkysfg0.png" alt="Webhook logs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you deploy this app to a cloud environment like Vercel, you will need to create a new endpoint in the Clerk dashboard that points to the deployed application instead of a localtunnel URL.&lt;/p&gt;

&lt;p&gt;Note that localtunnel will generate a random URL everytime we start the tunnel, which means everytime we are testing with webhooks locally, we will need to create a new endpoint in the Clerk dashboard. To solve this, we can ask localtunnel to provision a specific URL by adding a --subdomain argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lt &lt;span class="nt"&gt;--port&lt;/span&gt; 3000 &lt;span class="nt"&gt;--subdomain&lt;/span&gt; unique-url-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the requested URL is available, it will be provisioned for us instead of a randomly generated URL. This will allow us to reuse the same endpoint in the Clerk dashboard everytime we are testing locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhook Use Cases
&lt;/h2&gt;

&lt;p&gt;So now that we are receiving webhook events from Clerk, what can we do with it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Synchronization
&lt;/h3&gt;

&lt;p&gt;While Clerk’s own database acts as the primary source of truth for authentication and user management, you can create a copy of this data in your own database which gets updated asynchronously through webhooks. This allows you to query your own database for user data instead of relying on Clerk’s APIs, along with flexibility with the data model, complex queries, transactional guarantees, and real time behavior. This mechanism is called data synchronization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Driven Systems
&lt;/h3&gt;

&lt;p&gt;Some advanced use cases might rely on asynchronous workflows and background jobs, such as sending notifications to users or other external systems. These workflows can be triggered to execute when a webhook event is received, for example, sending new users an onboarding email to your application, or subscribing them to mailing lists for software updates and promotional campaigns. Such systems are called event driven systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bring Your Own Email/SMS
&lt;/h3&gt;

&lt;p&gt;Clerk allows you to bring your own Email and/or SMS servers for delivering auth-related messages which would usually be delivered by Clerk. Whenever an auth-related message is created, Clerk sends a webhook event with that message to your application. You can configure Clerk to not deliver these messages in the Clerk dashboard, and instead handle the delivery yourself by listening to the webhook events. (add links to HWR and docs)&lt;/p&gt;

&lt;p&gt;We will walk through implementing some of these use-cases in future articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhook Best Practices
&lt;/h2&gt;

&lt;p&gt;While setting up a simple webhook receiver for testing purposes is straightforward, operating a webhook-driven system in production requires some additional work. Clerk implements webhook deliveries through Svix, a service that specializes in handling webhook deliveries. Svix manages concerns like scalability, logging, retries, security, and delivery guarantees. However, we still need to ensure that our application is setup to receive and process webhooks correctly. Let’s explore some best practices for implementing webhook endpoints for production-ready applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;p&gt;Webhook deliveries are marked as successful or failed depending on the HTTP response returned by our webhook endpoint. A failed webhook delivery is retried with exponential backoff until it succeeds, ensuring that our application will never miss an event. The exponential backoff ensures that our application servers are not overloaded with retries and explode server resources.&lt;/p&gt;

&lt;p&gt;Our webhook endpoints should be setup to return a successful response (20x) if everything went well. But in case of an error during the processing of a webhook, we can simply throw an error response (40x or 50x) and expect a retry, which often succeeds. We can also monitor the webhook status in the Clerk dashboard, which provides observability into failed webhook deliveries and help discover issues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/clerk/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// process the event&lt;/span&gt;

    &lt;span class="c1"&gt;// everything went well&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// something went wrong&lt;/span&gt;
    &lt;span class="c1"&gt;// no changes were made to the database&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Since webhooks requests can originate on any remote server, webhook endpoints are vulnerable to attacks from malicious actors. Securing webhook endpoints is important to ensure malicious actors cannot make unwanted changes to your application.&lt;/p&gt;

&lt;p&gt;Svix adds security measures to webhook requests by adding a secure hash in the request headers created using a secret key. We can use the svix library in our webhook endpoint to validate that the request was sent by a legitimate Svix server and not a malicious actor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/clerk/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/headers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Webhook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svix&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webhookSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLERK_WEBHOOK_SECRET&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payloadString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headerPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svixHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svix-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;headerPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svix-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svix-timestamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;headerPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svix-timestamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svix-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;headerPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svix-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;wh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payloadString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;svixHeaders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get the webhook signing secret from the Clerk dashboard and store in a &lt;code&gt;CLERK_WEBHOOK_SECRET&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8r2svce1b53yvp5ybb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8r2svce1b53yvp5ybb1.png" alt="Webhook signing secret for adding secure validation to receivers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our webhook endpoint is fully secure and ready to be deployed to production servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selective Events
&lt;/h3&gt;

&lt;p&gt;In our walkthrough, we configured Clerk to trigger the webhook endpoint for all events. While this was useful for testing, we want to minimize the use of server resources by only processing events that we actually care about.&lt;/p&gt;

&lt;p&gt;For production endpoints, we can apply event filters in the Clerk dashboard by selecting the events that we are processing in the application, and leaving everything else unselected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0j39ri9d1nrikl7bpqa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0j39ri9d1nrikl7bpqa.png" alt="Filtering webhook events"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Webhooks play an important role in creating unified user experiences through integrations like data sync and event driven systems. In this article we setup a simple webhook endpoint, explored some use cases, and addressed concerns like security and error handling.&lt;/p&gt;

&lt;p&gt;We will dive deep into specific use cases around data sync and event driven systems in future blog posts and walk through implementing them into our projects.&lt;/p&gt;

&lt;p&gt;To learn more about webhooks, check out the ultimate webhooks guide, or to learn more about the webhook events exposed by Clerk, read the documentation.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
