<?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: Mike</title>
    <description>The latest articles on DEV Community by Mike (@grabbou).</description>
    <link>https://dev.to/grabbou</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%2F928339%2Fa7e33834-12bf-4287-8c91-21ac27f60e6c.jpg</url>
      <title>DEV Community: Mike</title>
      <link>https://dev.to/grabbou</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/grabbou"/>
    <language>en</language>
    <item>
      <title>Type-safe env variables</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Fri, 30 Sep 2022 10:24:05 +0000</pubDate>
      <link>https://dev.to/grabbou/type-safe-env-variables-5215</link>
      <guid>https://dev.to/grabbou/type-safe-env-variables-5215</guid>
      <description>&lt;p&gt;Almost all our projects have environmental variables. We use them to configure various aspects of our systems. What if they’re missing? What if they’re misconfigured?&lt;/p&gt;

&lt;p&gt;This is typically a challenge when working with TypeScript, which immediately warns you about missing properties as soon as you start accessing your variables from &lt;code&gt;process.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A quick fix would be to declare types for &lt;code&gt;process.env&lt;/code&gt; and call it a day. But can we do better?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Of course! 🎉&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the solution is not even TypeScript-specific at all. But its presence definitely prompts us to deal with that scenario sooner than later. Otherwise, the compiler isn’t happy!&lt;/p&gt;

&lt;h2&gt;
  
  
  High-level idea
&lt;/h2&gt;

&lt;p&gt;Define a schema for your &lt;code&gt;process.env&lt;/code&gt; object. Then, parse it and throw an error if validation wasn’t successful. Rather than accessing a raw process.env object, prefer to work with a parsed result.&lt;/p&gt;

&lt;p&gt;It will hold type information, have an appropriate shape, and as a bonus point - let you transform its stringified value to a primitive that your application expects (e.g. &lt;code&gt;PORT&lt;/code&gt; should really be a number!)&lt;/p&gt;

&lt;p&gt;If that’s nothing new to you, you’re good to stop reading at this point! However, if you want to check the potential implementation and learn about handling default values in development mode, keep reading (and consider subscribing for further articles)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: This post is inspired by &lt;a href="https://github.com/KATT/envsafe" rel="noopener noreferrer"&gt;env-safe by @alexdotjs &lt;/a&gt;and his &lt;a href="https://twitter.com/alexdotjs/status/1571993436613660674" rel="noopener noreferrer"&gt;recent Twitter reply to my thread&lt;/a&gt;, where he suggested handling environmental variables in Typescript with a generic schema validation library over a dedicated library. If you’re not following &lt;a href="https://twitter.com/alexdotjs" rel="noopener noreferrer"&gt;Alex&lt;/a&gt; on Twitter yet, you should definitely do it now!&lt;/p&gt;

&lt;p&gt;In this quick blog post, I will use one of &lt;a href="https://github.com/trpc/examples-next-prisma-starter/blob/next/src/server/env.js" rel="noopener noreferrer"&gt;TRPC's examples available on GitHub&lt;/a&gt; to explain this concept in real life and then show you how to handle default values in development mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;First, let’s define a schema to represent the type of environmental variables our application expects to work.&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;z&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;zod&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;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&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="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;OAUTH_CLIENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&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="na"&gt;OAUTH_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&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="na"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&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="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&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;To do so, we will use &lt;code&gt;z.object()&lt;/code&gt; and describe each property with an appropriate type. This will naturally look different if you already use a different schema library in your project.&lt;/p&gt;

&lt;p&gt;Next, we will validate process.env the object against our schema.&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;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&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="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;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❌ Invalid environment variables:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&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="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all environmental variables are present and of the correct type, the values will be accessible under &lt;code&gt;parsed.data&lt;/code&gt; and include typings. Otherwise, an error will be thrown.&lt;/p&gt;

&lt;p&gt;PS. You can find this code in one of TRPC's example projects, such as &lt;a href="https://github.com/trpc/examples-next-prisma-starter/blob/next/src/server/env.js" rel="noopener noreferrer"&gt;next-prisma-starter&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining default values in development mode
&lt;/h3&gt;

&lt;p&gt;One of the great features of &lt;code&gt;env-safe&lt;/code&gt; that I really fell in love with is &lt;code&gt;devDefault&lt;/code&gt;. Just like the name suggests, it lets you define default values for your environmental variables while in development.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1571991415370166273-156" src="https://platform.twitter.com/embed/Tweet.html?id=1571991415370166273"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1571991415370166273-156');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1571991415370166273&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;What’s super cool about this is that it lets you define and share some of the development settings with the rest of your team without the risk of using them in production. That’s definitely something that boolean conditionals don’t guarantee.&lt;/p&gt;

&lt;p&gt;Unfortunately, there’s nothing similar built into Zod. That’s not a surprise. After all, it’s a generic schema validation library, not a specialized env tool.&lt;/p&gt;

&lt;p&gt;Since the author of &lt;code&gt;env-safe&lt;/code&gt; library commented on Twitter that &lt;a href="https://twitter.com/alexdotjs/status/1571993436613660674" rel="noopener noreferrer"&gt;he’s using zod nowadays too&lt;/a&gt;, I figured it would be great to write an equivalent of devDefault that works with it.&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;TypeOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&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;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;onst&lt;/span&gt; &lt;span class="nx"&gt;withDevDefault&lt;/span&gt; &lt;span class="o"&gt;=&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="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodTypeAny&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;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TypeOf&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="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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NODE_ENV&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the function looks pretty simple, it was fun to write and play around with Typescript generics to make it work. Thankfully, the Zod documentation is pretty helpful in writing functions that take schema as an argument.&lt;/p&gt;

&lt;p&gt;Let’s take a look at what our schema could look like with default arguments applied:&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;z&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;zod&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;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;withDevDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&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="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;withDevDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&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;That’s pretty much it!&lt;/p&gt;

&lt;p&gt;You can now safely access your project's environmental variables and ensure they’re set. If not, you’ll learn about that as soon as your process exits with a non-zero code.&lt;/p&gt;




&lt;p&gt;In the upcoming series of articles, I’ll discuss deploying Node.js (and Typescript) projects to the AWS cloud with Docker and Terraform, where this approach to dealing with environmental variables will come in handy. If you’re interested in that topic, check in here from time to time!&lt;/p&gt;




&lt;p&gt;Thank you for reading,&lt;br&gt;
Mike&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Postgres database functions are your next favorite feature!</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Wed, 21 Sep 2022 20:42:03 +0000</pubDate>
      <link>https://dev.to/grabbou/postgres-database-functions-are-your-next-favorite-feature-42k2</link>
      <guid>https://dev.to/grabbou/postgres-database-functions-are-your-next-favorite-feature-42k2</guid>
      <description>&lt;p&gt;Imagine you’re building a system where users can perform transactions such as selling and buying tokens. For each token, every user has a dedicated wallet.&lt;/p&gt;

&lt;p&gt;Our database consists of four entities: &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Wallet&lt;/code&gt;, &lt;code&gt;Token&lt;/code&gt;, and &lt;code&gt;Transaction&lt;/code&gt;.&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%2F05mm2sdgj5x7stcmrwh7.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%2F05mm2sdgj5x7stcmrwh7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The transaction has a field &lt;code&gt;delta&lt;/code&gt; that is either a positive or negative number. When positive, that is the number of tokens purchased. When negative, it is how many tokens were sold.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;balance&lt;/code&gt; is a sum of all deltas from all transactions within a given wallet.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you calculate the balance?
&lt;/h2&gt;

&lt;p&gt;You’re building a query to retrieve all wallets that belong to a given user. You want to show how many tokens of each type the user has.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How would you approach such a query?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Before reading further, think for a few seconds, then compare your initial thoughts with my research. If there’s something I haven’t covered, share it with me on Twitter!&lt;/p&gt;

&lt;h3&gt;
  
  
  Use an aggregate function to compute the balance by summarising all transactions.
&lt;/h3&gt;

&lt;p&gt;This is probably the most popular and a solution you should start with… unless you’re using Prisma! &lt;/p&gt;

&lt;p&gt;Postgres (and other relational databases) were designed to work with large datasets, and with enough resources on a machine (typically, they scale vertically), it shouldn’t be a problem for them to handle hundreds of thousands of records relatively fast.&lt;/p&gt;

&lt;p&gt;However, if you’re like me, you probably think it’s better to save that computational power for queries we can’t optimize further.&lt;/p&gt;

&lt;p&gt;If that doesn’t convince you yet, here’s another thing. While Prisma supports aggregation via &lt;a href="https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#aggregate" rel="noopener noreferrer"&gt;aggregate API&lt;/a&gt;, selecting fields, joining related tables, and aggregating in a single query is impossible.&lt;/p&gt;

&lt;p&gt;That means we’re back writing good old SQL. I’d rather look for alternatives and enjoy TypeScript-type safety at the early stage of my product lifecycle as long as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Select all transactions and then reduce them into balance for each wallet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;users&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;user&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&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;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&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="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is probably the least performant solution with &lt;code&gt;O(n^2)&lt;/code&gt; complexity, which is typical when dealing with nested loops (you should be able to speed that up to &lt;code&gt;O(n)&lt;/code&gt; if you have recently done any coding challenges). Performing such an operation with hundreds of thousands of records doesn’t sound right, regardless of time complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store a computed balance of each wallet after every transaction
&lt;/h3&gt;

&lt;p&gt;While this option sounds intuitive at first, it is dangerous. We duplicate the data in two places and increase the likelihood of data corruption. As soon as transactions start flowing throughout the system, you’re never sure if the value you’re reading is accurate.&lt;/p&gt;

&lt;p&gt;That’s unfortunate because having direct access to a balance field on a Wallet would be the most intuitive and performant way of accessing that data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I told you that creating such a computed field was possible without risking your data integrity?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Say hello to database functions! 👋&lt;/p&gt;

&lt;h2&gt;
  
  
  Trigger functions
&lt;/h2&gt;

&lt;p&gt;If this is your first time working with Postgres, let me tell you one thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That was a great choice!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s an amazing open-source relational database packed with many features that will come in handy as your application grows. Trigger functions are one of them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re interested in other advanced Postgres features, make sure to follow my blog. I will definitely be exploring them as I advance in my development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On a high level, Postgres lets you register a callback that will run before or after a specified operation happens in the database, such as &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can do many great things with database functions, such as performing checks on the data that can’t be expressed as constraints or updating records in related tables.&lt;/p&gt;

&lt;p&gt;Does that sound familiar to you? It is exactly what we’ll be doing today!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Postgres function to update the balance on every transaction
&lt;/h3&gt;

&lt;p&gt;There are two ways to create a database function - programmatically, by running a query, or by using the Supabase 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%2Fa6vt1kgn23g1wb06cuep.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%2Fa6vt1kgn23g1wb06cuep.png" alt="Supabase dashboard has a comprehensive interface for creating triggers and functions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Either way, you will need to write a bit of &lt;code&gt;PL/pgSQL&lt;/code&gt;. It’s a procedural programming language supported by Postgres.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sounds scary? Don’t worry.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You should feel at home if you have worked with SQL before. If you look at the function below, without going into many details, you will notice that there is a fair amount of good old SQL in it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;calculate_balance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Wallet"&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Transaction"&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&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;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to break that down into pieces!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;calculate_balance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="cm"&gt;/* your function goes here */&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This statement creates a function and saves it under &lt;code&gt;calculate_balance&lt;/code&gt; in the global namespace.&lt;/p&gt;

&lt;p&gt;If you’re wondering what’s that &lt;code&gt;$$&lt;/code&gt; about, it’s an alternative symbol for enclosing string literals. The body of your function is a string literal. If we used single or double quotes, we would have to escape any nested occurrences of these characters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Wallet"&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Transaction"&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"walletId"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is our function body. It’s an &lt;code&gt;UPDATE&lt;/code&gt; statement. What is worth noting is that there’s a &lt;code&gt;new&lt;/code&gt; variable that includes a row that results from the operation that triggered our function. If our function was triggered by an &lt;code&gt;INSERT&lt;/code&gt; operation, this would be a newly added record. If it was an &lt;code&gt;UPDATE&lt;/code&gt;, it would hold the latest data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If we’re dealing with &lt;code&gt;DELETE&lt;/code&gt; operation, &lt;code&gt;new&lt;/code&gt; will naturally be &lt;code&gt;null&lt;/code&gt; and we should read from &lt;code&gt;old&lt;/code&gt;. We’re not handling that case in our function yet. It will most likely require an &lt;code&gt;IF&lt;/code&gt; condition somewhere inside function body to check whether &lt;code&gt;TG_OP&lt;/code&gt; variable is &lt;code&gt;delete&lt;/code&gt; and if yes, choose &lt;code&gt;old&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Creating a trigger to run our function
&lt;/h3&gt;

&lt;p&gt;Now that we have created a function, it is time to define what will trigger it. Like before, what we will be writing is going to be a SQL-like query that describes the conditions under which our function should execute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;on_new_transaction&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Transaction"&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;calculate_balance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we create &lt;code&gt;on_new_transaction&lt;/code&gt; trigger that we want to run after either insert or update operation is performed on the &lt;code&gt;Transaction&lt;/code&gt; table. We want our function &lt;code&gt;calculate_balance()&lt;/code&gt; to run for every added record.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An alternative to running for every added record would be to run for every statement. There are use cases where that makes sense. However, in our scenario, we want to recalculate balance after every transaction, as they may concern different wallets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Registering a function in the database
&lt;/h3&gt;

&lt;p&gt;Now that our entire trigger is ready, it is time to register it within our database.&lt;/p&gt;

&lt;p&gt;The easiest way to register a function would be to execute that query. I personally use Supabase SQL Editor and occasionally save queries for future reuse.&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%2Fqcbsq7rhbejtq8fx2jhb.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%2Fqcbsq7rhbejtq8fx2jhb.png" alt="SQL Editor lets you run queries and save them for future reuse"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After we copy the query into SQL Editor, we hit &lt;code&gt;RUN&lt;/code&gt;. The operation should complete successfully.&lt;/p&gt;

&lt;p&gt;To verify that our function and trigger were added, we can go to the Database tab and select Triggers from the left menu.&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%2Fxyqozc868mka87hdaj1j.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%2Fxyqozc868mka87hdaj1j.png" alt="While Triggers are marked as Alpha, this only concerns the UI, not functions themselves."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything worked well, we should see the trigger right in the middle of the page and the conditions that will make it run.&lt;/p&gt;

&lt;p&gt;Now, going to Functions:&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%2Fp75g5mewch4pk7t2esbk.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%2Fp75g5mewch4pk7t2esbk.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We should see our newly created function at the top of the screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That’s it!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We just created a Postgres function that computes a field each time underlying data changes. It’s almost like a cache, but built-in. And because it’s running on a database level, we don’t have to worry about data integrity, which would be quite a challenge if done manually.&lt;/p&gt;

&lt;p&gt;Thanks for reading,&lt;br&gt;
Mike&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
