<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Server Surfer</title>
        <link>https://paragraph.com/@serversurfer</link>
        <description>undefined</description>
        <lastBuildDate>Sat, 09 May 2026 12:48:59 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Server Surfer</title>
            <url>https://storage.googleapis.com/papyrus_images/a57b5bb9f0590ff227bd559b3a9d9a22d4f9103da7e4288afad312c7b7f82192.png</url>
            <link>https://paragraph.com/@serversurfer</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Building an ecosystem of products]]></title>
            <link>https://paragraph.com/@serversurfer/building-an-ecosystem-of-products</link>
            <guid>6ZoQ1dd962NmirCqtIkG</guid>
            <pubDate>Thu, 18 Nov 2021 14:57:27 GMT</pubDate>
            <description><![CDATA[Everyone has ideas. Sometime&apos;s called visions. I&apos;d know, because I&apos;ve built other people&apos;s ideas while trying to build mine. Still, this is pretty normal while growing up during the internet&apos;s golden age. Most of this generation is proficient enough in multiple skill sets, that executing on an array of ideas would be the most logical course of action. Not only can this introduce alternate streams of income, it also builds the foundation of your brand. We all look at c...]]></description>
            <content:encoded><![CDATA[<p>Everyone has ideas.</p><p>Sometime&apos;s called visions.</p><p>I&apos;d know, because I&apos;ve built other people&apos;s ideas while trying to build mine.</p><p>Still, this is pretty normal while growing up during the internet&apos;s golden age. Most of this generation is proficient enough in multiple skill sets, that executing on an array of ideas would be the most logical course of action. Not only can this introduce alternate streams of income, it also builds the foundation of your brand.</p><p>We all look at creating brands as telling a story. But in narcissistic fashion, I rather look at it as building worlds. With entities that include protagonist, antagonist and the environment. An important brand will touch on a multitude of problems. Our favourite brands like Disney, encapsulates companies from resorts to software development studios. But the brilliance is how all these companies interact with each other. The network, a web spun by the parent company — Walt Disney. This can be achieved with choosing a north star. Principles that act as guidelines to ensure consistency.</p><p>There&apos;s a coined term, &quot;Startup Studio&quot;. It&apos;s the idea of creating many different entities under the roof of one company. It&apos;s very easy to build ideas as they come into your head. Especially when you&apos;re a developer. But that&apos;s the point of this post. A letter to the universe. To cement my path towards building a studio with all the products intertwined.</p><p>The way I&apos;ve been approaching this concept, while still keeping that dopamine boost of spontaneity, is seeing where I can recycle ideas. I started with an initial principal. I wanted to build products for creators. Many ideas failed, but that&apos;s where the recycling comes in to play. A product may not be a market success, but as you break down the idea atom by atom, new ideas are created with the same initial principal. I&apos;m not a rocket scientist, but I believe that&apos;s how atoms work.</p><p>There is second pillar when it comes to building a product studio. It&apos;s the act of creating a system of processes. Since the majority of products being built wont be a success, It&apos;s important to be able to fail fast. For me, this meant the following:</p><ul><li><p>Choose a studio wide framework and coding language</p></li><li><p>Create a SaaS starter (<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.1upblitz.com/">1upblitz.com</a>)</p></li><li><p>Notion templates</p></li><li><p>Reusable React components</p></li><li><p>Fully coded landing pages riddled with Lorem Ipsum &amp; email captures</p></li><li><p>Marketing tasks for first 100 users</p></li></ul><p>The funny thing is, most of all these processes can be recycled into it&apos;s own product. They can all be connected, and leverage each others audience to funnel into more sales.</p><p>On a final note, don&apos;t get too attached to ideas. You never know what will stick. But it&apos;s important to fail, and fail fast. Keep it moving. Recycle. Create an ecosystem. Determine principals and processes and it will make the journey easier for you.</p>]]></content:encoded>
            <author>serversurfer@newsletter.paragraph.com (Server Surfer)</author>
        </item>
        <item>
            <title><![CDATA[Extending types for models in prisma]]></title>
            <link>https://paragraph.com/@serversurfer/extending-types-for-models-in-prisma</link>
            <guid>jWPdVslGmJRcuAqH7NhR</guid>
            <pubDate>Thu, 18 Nov 2021 14:56:48 GMT</pubDate>
            <description><![CDATA[Quick tips when working with Prisma. Say you have a model with a Json type attribute that you want to type, because the default types don&apos;t contain information about the keys and values inside the json object we&apos;ll need to manually type them. Say this is our User model with the extendedProfile attribute appended at the bottom:model User { id Int @default(autoincrement()) @id createdAt DateTime @default(now()) updatedAt DateTime @updatedAt email String @unique hashedPassword String? ...]]></description>
            <content:encoded><![CDATA[<p><strong>Quick tips when working with Prisma.</strong> Say you have a <code>model</code> with a <code>Json</code> type attribute that you want to type, because the default types don&apos;t contain information about the keys and values inside the json object we&apos;ll need to manually type them.</p><p>Say this is our <code>User</code> model with the <code>extendedProfile</code> attribute appended at the bottom:</p><pre data-type="codeBlock" text="model User {

  id			 Int	   @default(autoincrement()) @id

  createdAt	  DateTime  @default(now())

  updatedAt	  DateTime  @updatedAt

  email		  String	@unique

  hashedPassword String?

  role		   String	@default(&quot;user&quot;)

  sessions	   Session[]

  extendedProfile Json?

}
"><code>model User {

  id			 <span class="hljs-built_in">Int</span>	   <span class="hljs-meta">@default(autoincrement())</span> <span class="hljs-meta">@id</span>

  createdAt	  DateTime  <span class="hljs-meta">@default(now())</span>

  updatedAt	  DateTime  <span class="hljs-meta">@updatedAt</span>

  email		  String	<span class="hljs-meta">@unique</span>

  hashedPassword String?

  role		   String	<span class="hljs-meta">@default(<span class="hljs-string">"user"</span>)</span>

  sessions	   Session[]

  extendedProfile Json?

}
</code></pre><p>We can create a <code>types.ts</code> file and export an <code>ExtendedUser</code> type.</p><pre data-type="codeBlock" text="export type ExtendedUser = User &amp; {

  extendedProfile: {

    firstName: string

    lastName: string

}

}
"><code>export <span class="hljs-keyword">type</span> ExtendedUser <span class="hljs-operator">=</span> User <span class="hljs-operator">&#x26;</span> {

  extendedProfile: {

    firstName: <span class="hljs-keyword">string</span>

    lastName: <span class="hljs-keyword">string</span>

}

}
</code></pre><p>Prisma also comes with some useful methods. Let’s say we’re working with a model that has a relation reference with another model.</p><p>For example, if the <code>User</code> model has a one-to-many relation with an <code>Order</code> model.</p><pre data-type="codeBlock" text="model User {

  id			 Int	   @default(autoincrement()) @id

  createdAt	  DateTime  @default(now())

  updatedAt	  DateTime  @updatedAt

  email		  String	@unique

  hashedPassword String?

  role		   String	@default(&quot;user&quot;)

  sessions	   Session[]

  extendedProfile Json?

  orders Order[]

}

model Order {

  id		Int	  @default(autoincrement()) @id

  createdAt DateTime @default(now())

  updatedAt DateTime @updatedAt

  user User @relation(fields: [userId], references: [id])

  userId Int

}
"><code>model User {

  id			 <span class="hljs-built_in">Int</span>	   <span class="hljs-meta">@default(autoincrement())</span> <span class="hljs-meta">@id</span>

  createdAt	  DateTime  <span class="hljs-meta">@default(now())</span>

  updatedAt	  DateTime  <span class="hljs-meta">@updatedAt</span>

  email		  String	<span class="hljs-meta">@unique</span>

  hashedPassword String?

  role		   String	<span class="hljs-meta">@default(<span class="hljs-string">"user"</span>)</span>

  sessions	   Session[]

  extendedProfile Json?

  orders Order[]

}

model Order {

  id		<span class="hljs-built_in">Int</span>	  <span class="hljs-meta">@default(autoincrement())</span> <span class="hljs-meta">@id</span>

  createdAt DateTime <span class="hljs-meta">@default(now())</span>

  updatedAt DateTime <span class="hljs-meta">@updatedAt</span>

  user User <span class="hljs-meta">@relation(fields: [userId], references: [id])</span>

  userId <span class="hljs-built_in">Int</span>

}
</code></pre><p>Inside <code>types.ts</code> we can import <code>Prisma</code> and use the <code>GetPayload</code> method it provides. So in our case our <code>ExtendedUser</code> type will look like this:</p><pre data-type="codeBlock" text="import { Prisma } from &apos;@prisma/client&apos;

export type ExtendedUser = Prisma.UserGetPayload&lt;{ include: { order: true } }&gt; &amp; {

  extendedProfile: {

    firstName: string

    lastName: string

}

}
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">Prisma</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@prisma/client'</span>

<span class="hljs-title">export</span> <span class="hljs-title"><span class="hljs-keyword">type</span></span> <span class="hljs-title">ExtendedUser</span> <span class="hljs-operator">=</span> <span class="hljs-title">Prisma</span>.<span class="hljs-title">UserGetPayload</span><span class="hljs-operator">&#x3C;</span>{ <span class="hljs-title">include</span>: { <span class="hljs-title">order</span>: <span class="hljs-title"><span class="hljs-literal">true</span></span> } }<span class="hljs-operator">></span> <span class="hljs-operator">&#x26;</span> {

  <span class="hljs-title">extendedProfile</span>: {

    <span class="hljs-title">firstName</span>: <span class="hljs-title"><span class="hljs-keyword">string</span></span>

    <span class="hljs-title">lastName</span>: <span class="hljs-title"><span class="hljs-keyword">string</span></span>

}

}
</code></pre><p><code>GetPayload</code> <em>will always start with the model name. In our case </em><code>UserGetPayload</code></p>]]></content:encoded>
            <author>serversurfer@newsletter.paragraph.com (Server Surfer)</author>
        </item>
        <item>
            <title><![CDATA[Debounce and persist event targeting in react]]></title>
            <link>https://paragraph.com/@serversurfer/debounce-and-persist-event-targeting-in-react</link>
            <guid>XWXOGPngWBb43eeEbjDO</guid>
            <pubDate>Thu, 18 Nov 2021 14:54:05 GMT</pubDate>
            <description><![CDATA[Recently I worked on a search component that would communicate with an API that uses ElasticSearch for indexing. It&apos;s common to have a search input, that as you type will auto query and update the list on the frontend. So I used a text input, and inside the `onChange` event would pass the `event.target.value` as a parameter to the API url. It would look something like this (abbreviated for readability):const searchQueryFunction = (queryText) => { const res = await fetch(`/products.json?_...]]></description>
            <content:encoded><![CDATA[<p>Recently I worked on a search component that would communicate with an API that uses ElasticSearch for indexing. It&apos;s common to have a search input, that as you type will auto query and update the list on the frontend.</p><p>So I used a text input, and inside the `onChange` event would pass the `event.target.value` as a parameter to the API url.</p><p>It would look something like this (abbreviated for readability):</p><pre data-type="codeBlock" text="const searchQueryFunction = (queryText) =&gt; {

    const res = await fetch(`/products.json?_page=${page}&amp;_limit=10&amp;_q=${queryText}&amp;_total=50`, {
    method: &apos;GET&apos;,
    headers: {
     &apos;Access-Control-Allow-Origin&apos;: &apos;*&apos;
    }
  })

  const responseData = await res.json()

  setItems({items: responseData.data})
}

&lt;input type=&quot;text&quot; placeholder=&quot;Search...&quot; onChange=((e) =&gt; searchQueryFunction(e.target.value)) /&gt;
"><code>const searchQueryFunction <span class="hljs-operator">=</span> (queryText) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {

    const res <span class="hljs-operator">=</span> await fetch(`<span class="hljs-operator">/</span>products.json?_page<span class="hljs-operator">=</span>${page}<span class="hljs-operator">&#x26;</span>_limit<span class="hljs-operator">=</span><span class="hljs-number">10</span><span class="hljs-operator">&#x26;</span>_q<span class="hljs-operator">=</span>${queryText}<span class="hljs-operator">&#x26;</span>_total<span class="hljs-operator">=</span><span class="hljs-number">50</span>`, {
    method: <span class="hljs-string">'GET'</span>,
    headers: {
     <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'*'</span>
    }
  })

  const responseData <span class="hljs-operator">=</span> await res.json()

  setItems({items: responseData.data})
}

<span class="hljs-operator">&#x3C;</span>input <span class="hljs-keyword">type</span><span class="hljs-operator">=</span><span class="hljs-string">"text"</span> placeholder<span class="hljs-operator">=</span><span class="hljs-string">"Search..."</span> onChange<span class="hljs-operator">=</span>((e) <span class="hljs-operator">=</span><span class="hljs-operator">></span> searchQueryFunction(e.target.<span class="hljs-built_in">value</span>)) <span class="hljs-operator">/</span><span class="hljs-operator">></span>
</code></pre><p>But I quickly found out how taxing this was for the browser and server. As I would search for something like &quot;Minivan&quot;, it would query each letter. This is where a debouncer comes in. There are libraries like `lodash` that offer a function, but wanted to keep my `package.json` light.</p><p>A debouncer is just a fancy `setTimeout`, so I created a `Debouncer` class that takes 2 parameters. A function and time.</p><p>It looks like this:</p><pre data-type="codeBlock" text="export default class Debouncer {
  timeout: null | ReturnType&lt;typeof setTimeout&gt;
  n: number
  func: Function

  constructor(func, n) {
    this.timeout = null
    this.n = n || 500
    this.func = func
  }

  execute = (e) =&gt; {
    this.cancel()
    this.timeout = setTimeout(() =&gt; {
      this.func(e)
    }, this.n)
  }

  cancel = () =&gt; {
    if (this.timeout) clearTimeout(this.timeout)
  }
}
"><code>export default class Debouncer {
  timeout: null <span class="hljs-operator">|</span> ReturnType<span class="hljs-operator">&#x3C;</span>typeof setTimeout<span class="hljs-operator">></span>
  n: number
  func: Function

  <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">func, n</span>) </span>{
    <span class="hljs-built_in">this</span>.timeout <span class="hljs-operator">=</span> null
    <span class="hljs-built_in">this</span>.n <span class="hljs-operator">=</span> n <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-number">500</span>
    <span class="hljs-built_in">this</span>.func <span class="hljs-operator">=</span> func
  }

  execute <span class="hljs-operator">=</span> (e) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    <span class="hljs-built_in">this</span>.cancel()
    <span class="hljs-built_in">this</span>.timeout <span class="hljs-operator">=</span> setTimeout(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
      <span class="hljs-built_in">this</span>.func(e)
    }, <span class="hljs-built_in">this</span>.n)
  }

  cancel <span class="hljs-operator">=</span> () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.timeout) clearTimeout(<span class="hljs-built_in">this</span>.timeout)
  }
}
</code></pre><p>Inside the React component, initialize the Debouncer and pass it the <code>searchQueryFunction</code></p><pre data-type="codeBlock" text="const debouncer = new Debouncer((e) =&gt; searchQueryFunction(e))
"><code><span class="hljs-keyword">const</span> debouncer = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Debouncer</span>(<span class="hljs-function">(<span class="hljs-params">e</span>) =></span> <span class="hljs-title function_">searchQueryFunction</span>(e))
</code></pre><p>Then create a new function that will execute the <code>searchQueryFunction</code> through the debouncer</p><pre data-type="codeBlock" text="const execDebouncer = (e) =&gt; debouncer.execute(e)
"><code>const execDebouncer <span class="hljs-operator">=</span> (e) <span class="hljs-operator">=</span><span class="hljs-operator">></span> debouncer.execute(e)
</code></pre><p>Now inside the input <code>onChange</code> event, well replace calling the <code>searchQueryFunction</code> directly and use the new <code>execDebouncer</code></p><pre data-type="codeBlock" text="&lt;input type=&quot;text&quot; onChange={(e) =&gt; execDebouncer(e)} /&gt;
"><code><span class="hljs-operator">&#x3C;</span>input <span class="hljs-keyword">type</span><span class="hljs-operator">=</span><span class="hljs-string">"text"</span> onChange<span class="hljs-operator">=</span>{(e) <span class="hljs-operator">=</span><span class="hljs-operator">></span> execDebouncer(e)} <span class="hljs-operator">/</span><span class="hljs-operator">></span>
</code></pre><p>But.. you&apos;ll notice some errors in the log:</p><blockquote><p>&quot;This synthetic event is reused for performance reasons. If you&apos;re seeing this, you&apos;re accessing the method currentTarget on a released/nullified synthetic event. This is a no-op function. If you must keep the original synthetic event around, use event.persist()&quot;</p></blockquote><p>This is because the event is lost, while the function is being debounced for 500ms. To fix this, simply add <code>e.persist()</code> inside the <code>execDebouncer</code> function</p><pre data-type="codeBlock" text="const execDebouncer = (e) =&gt; {
  e.persist()
  return debouncer.execute(e)
}
"><code>const execDebouncer <span class="hljs-operator">=</span> (e) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  e.persist()
  <span class="hljs-keyword">return</span> debouncer.execute(e)
}
</code></pre><p>I put together a little example, with an included mock api:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://codesandbox.io/embed/pensive-swartz-cvsx1">https://codesandbox.io/embed/pensive-swartz-cvsx1</a></p>]]></content:encoded>
            <author>serversurfer@newsletter.paragraph.com (Server Surfer)</author>
        </item>
    </channel>
</rss>