<?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: Thorr ⚡️ codinsonn.dev</title>
    <description>The latest articles on DEV Community by Thorr ⚡️ codinsonn.dev (@codinsonn).</description>
    <link>https://dev.to/codinsonn</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%2F280796%2F4a8ad32e-b9b2-45fa-a13f-40dbd0f7de72.png</url>
      <title>DEV Community: Thorr ⚡️ codinsonn.dev</title>
      <link>https://dev.to/codinsonn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codinsonn"/>
    <language>en</language>
    <item>
      <title>Zod and the Joy of Single Sources of Truth</title>
      <dc:creator>Thorr ⚡️ codinsonn.dev</dc:creator>
      <pubDate>Wed, 29 May 2024 10:33:19 +0000</pubDate>
      <link>https://dev.to/codinsonn/the-joy-of-single-sources-of-truth-277o</link>
      <guid>https://dev.to/codinsonn/the-joy-of-single-sources-of-truth-277o</guid>
      <description>&lt;p&gt;If you love Typescript and haven't been living under a rock for the past 2 years, you've likely heard of &lt;strong&gt;&lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

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

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



 &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Zod&lt;/strong&gt; is a Typescript-first schema validation library, aimed at maximum TS compatibility. It powers popular libraries like &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;TRPC&lt;/a&gt; &amp;amp; has a &lt;a href="https://zod.dev/?id=ecosystem" rel="noopener noreferrer"&gt;big ecosystem&lt;/a&gt; around it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An important design goal is to provide type safety at runtime, not just build time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The major strength of Zod is that it can extract types from your validation schema&lt;/em&gt;. For example, this is how you would define primitive data validators in Zod and extract the types&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="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="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// - Strings -&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StringValue&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;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StringValue&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;StringValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; string&lt;/span&gt;

&lt;span class="nx"&gt;StringValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="c1"&gt;// =&amp;gt; ✅ inferred result = string&lt;/span&gt;
&lt;span class="nx"&gt;StringValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; throws ZodError at runtime&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what it would look like for other primitive types:&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;// - Numbers -&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NumberValue&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;number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NumberValue&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;NumberValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; number&lt;/span&gt;

&lt;span class="nx"&gt;NumberValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ✅ inferred type = number&lt;/span&gt;
&lt;span class="nx"&gt;NumberValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; throws ZodError at runtime&lt;/span&gt;

&lt;span class="c1"&gt;// - Booleans -&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BooleanValue&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;boolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BooleanValue&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;BooleanValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; boolean&lt;/span&gt;

&lt;span class="nx"&gt;BooleanValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="c1"&gt;// =&amp;gt; ✅ type = boolean&lt;/span&gt;
&lt;span class="nx"&gt;BooleanValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ZodError&lt;/span&gt;

&lt;span class="c1"&gt;// - Dates -&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DateValue&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;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DateValue&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;DateValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; Date&lt;/span&gt;

&lt;span class="nx"&gt;DateValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ✅ type = Date&lt;/span&gt;
&lt;span class="nx"&gt;DateValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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 Friday&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// also throws&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Already, Zod acts as a 'single source of truth' for validation and types.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To understand the power of Zod, you need to think of it as a &lt;strong&gt;single source of truth&lt;/strong&gt; for your data structures:&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;// This is just a type&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SomeDatastructure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
    &lt;span class="na"&gt;isStudent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;birthdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// This provides both types AND validation&lt;/span&gt;
&lt;span class="c1"&gt;// + it can never get out of sync ✅&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SomeDatastructureSchema&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;name&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;age&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;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;isStudent&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;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;birthdate&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;date&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;In this article, I'll build up to more advanced use cases of Zod, and how it can be used as a single source of truth for not just types and validation, but any datastructure definition in your entire codebase.&lt;/p&gt;




&lt;h2&gt;
  
  
  Convert with Type Coercion
&lt;/h2&gt;

&lt;p&gt;Sometimes, you don't want things like &lt;code&gt;"42"&lt;/code&gt; to be rejected when you're expecting a number.&lt;/p&gt;

&lt;p&gt;You want to convert them to the correct type instead:&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;NumberValue&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="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;= Prefix with .coerce to enable type coercion&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NumberValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;From the zod.dev docs:&lt;/strong&gt;  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"During the parsing step, the input is passed through the String() function,&lt;br&gt;&lt;br&gt;
which is a JavaScript built-in for coercing data into strings."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can do this for &lt;em&gt;any&lt;/em&gt; primitive data type:&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;StringValue&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="nx"&gt;coerce&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="c1"&gt;// When parsing -&amp;gt; String(input)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StringValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; "42"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DateValue&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="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; new Date(input)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-05-29&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; Date (Wed May 29 2024 00:00:00 GMT+0200)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BooleanValue&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="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; Boolean(input)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someBoolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BooleanValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="c1"&gt;// =&amp;gt; false (falsy value)&lt;/span&gt;

&lt;span class="c1"&gt;// -!- Caveat: strings are technically truthy&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anotherBoolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BooleanValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; true &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Validation Errors
&lt;/h2&gt;

&lt;p&gt;So let's look at what happens when you try to parse invalid data:&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;try&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;someNumber&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;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is not a number&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* Throws 'ZodError' with a .issues array:
        [ {
            code: 'invalid_type',
            expected: 'number',
            received: 'string',
            path: [],
            message: 'Expected string, received number',
        } ]
    */&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;From the zod.dev docs:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All validation errors thrown by Zod are instances of &lt;code&gt;ZodError&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
ZodError is a subclass of Error; you can create your own instance easily:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;z&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;MyCustomError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZodError&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Each ZodError has an &lt;code&gt;issues&lt;/code&gt; property that is an array of &lt;code&gt;ZodIssues&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Each issue documents a problem that occurred during validation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But, customizing error messages is easier than you might think:&lt;/p&gt;




&lt;h3&gt;
  
  
  Custom Error Messages
&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;NumberValue&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;number&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;Please provide a number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; Throws ZodError [{ message: "Please provide a number", code: ... }]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MinimumValue&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;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&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="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;Value must be at least 10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; Throws ZodError [{ message: "Value must be at least 10", code: ... }]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MaximumValue&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;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&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="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;Value must be at most 100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; Throws ZodError [{ message: "Value must be at most 100", code: ... }]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Going beyond TS by narrowing types
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.min()&lt;/code&gt; and &lt;code&gt;.max()&lt;/code&gt; methods on &lt;code&gt;ZodNumber&lt;/code&gt; from the previous examples are just the tip of the iceberg. They're a great example of what's possible beyond typescript-like type validation and narrowing.&lt;/p&gt;

&lt;p&gt;For example, you can also use &lt;code&gt;.min()&lt;/code&gt;, &lt;code&gt;.max()&lt;/code&gt; and even &lt;code&gt;.length()&lt;/code&gt; on strings and arrays:&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;// e.g. TS can't enforce a minimum length (👇) on a string&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CountryNameValidator&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;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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="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;Country name must be at least 4 characters long&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// ... or an exact length on an array (👇) *&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PointValue3D&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;array&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;number&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&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;Coördinate must have x, y, z values&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;blockquote&gt;
&lt;p&gt;*Though you could probably hack it together with Tuples 🤔&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Advanced Types: Enums, Tuples and more
&lt;/h2&gt;

&lt;p&gt;Speaking of, more advanced types like Tuples or Enums are also supported by Zod:&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;PointValue2D&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;tuple&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;number&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;number&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; [number, number]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Direction&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;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Left&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;Right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; "Left" | "Right"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, for enums, you could provide an actual enum:&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="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;DirectionEnum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Right&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;Direction&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;enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DirectionEnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; DirectionEnum&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to use a zod enum to autocomplete options from, you can:&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;Languages&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;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PHP&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;Ruby&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;Typescript&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;Python&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Languages&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Languages&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; "PHP" | "Ruby" | "Typescript" | "Python"&lt;/span&gt;

&lt;span class="c1"&gt;// You can use .enum for a native-like (👇) experience to pick options&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myFavoriteLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; "Typescript"&lt;/span&gt;

&lt;span class="c1"&gt;// ... which is the equivalent of:&lt;/span&gt;

&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Languages&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;PHP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PHP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Ruby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ruby&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;TypeScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Typescript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Python&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Python&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;myFavoriteLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; "Typescript"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  There's more:
&lt;/h3&gt;

&lt;p&gt;Zod also supports more advanced types like &lt;code&gt;union()&lt;/code&gt;, &lt;code&gt;intersection()&lt;/code&gt;, &lt;code&gt;promise()&lt;/code&gt;, &lt;code&gt;lazy()&lt;/code&gt;, &lt;code&gt;nullable()&lt;/code&gt;, &lt;code&gt;optional()&lt;/code&gt;, &lt;code&gt;array()&lt;/code&gt;, &lt;code&gt;object()&lt;/code&gt;, &lt;code&gt;record()&lt;/code&gt;, &lt;code&gt;map()&lt;/code&gt;, &lt;code&gt;set()&lt;/code&gt;, &lt;code&gt;function()&lt;/code&gt;, &lt;code&gt;instanceof()&lt;/code&gt;, &lt;code&gt;promise()&lt;/code&gt; and &lt;code&gt;unknown()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However,&lt;/strong&gt; if you're interested in learning the ins and outs, I highly recommend checking out the awesome &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod documentation&lt;/a&gt;, &lt;em&gt;after&lt;/em&gt; you've read this article.&lt;/p&gt;

&lt;p&gt;I won't go into further detail on these, as this is not just about Zod and how to use it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It's about using schemas as single sources of truth.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Bringing it together in Schemas
&lt;/h1&gt;




&lt;p&gt;Validating individual fields is great, but typically, you'll want to validate entire objects.&lt;/p&gt;

&lt;p&gt;You can easily do this with &lt;code&gt;z.object()&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;User&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;name&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;age&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;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;isStudent&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;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;birthdate&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;date&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;Here too, the aliased schema type can be used for editor hints or instant feedback:&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;// Alias the Schema to the inferred type&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Common pattern&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isStudent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;birthdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1980-01-01&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="c1"&gt;// =&amp;gt; ✅ Yup, that satisfies the `User` type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like in Typescript, if you want to derive another user type from the &lt;code&gt;User&lt;/code&gt; schema, you can:&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;// Extend the User schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;isAdmin&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;boolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Admin&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Admin&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; { name: string, age: number, isStudent: boolean, birthdate: Date, isAdmin: boolean }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other supported methods are &lt;code&gt;pick()&lt;/code&gt; and &lt;code&gt;omit()&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="c1"&gt;// Omit fields from the User schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PublicUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;birthdate&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PublicUser&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;PublicUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; { name: string, age: number, isStudent: boolean }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pick fields from the User schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MinimalUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&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="na"&gt;age&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MinimalUser&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;MinimalUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; { name: string, age: number }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need to represent a collection of users...?&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;// Use a z.array() with the 'User' schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Team&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;members&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;array&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="c1"&gt;// &amp;lt;- Reusing the 'User' schema&lt;/span&gt;
    &lt;span class="na"&gt;teamName&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="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Team&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Team&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; { teamName: string, members: User[] }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... or maybe you want a lookup object?&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;// Use a z.record() for datastructure to e.g. to look up users by their ID&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserLookup&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;record&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="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// where z.string() is the type of the id&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserLookup&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;UserLookup&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; { [key: string]: User }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's no understatement to say that Zod is already a powerful tool for defining data structures in your codebase.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But it can be so much more than that.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why single sources of truth?
&lt;/h2&gt;




&lt;blockquote&gt;
&lt;p&gt;Think of all the places you might need to repeat certain field definitions:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;✅ Types&lt;/li&gt;
&lt;li&gt;✅ Validation&lt;/li&gt;
&lt;li&gt;✅ Database Models&lt;/li&gt;
&lt;li&gt;✅ API Inputs &amp;amp; Responses&lt;/li&gt;
&lt;li&gt;✅ Form State&lt;/li&gt;
&lt;li&gt;✅ Documentation&lt;/li&gt;
&lt;li&gt;✅ Mocks &amp;amp; Test Data&lt;/li&gt;
&lt;li&gt;✅ GraphQL schema &amp;amp; query definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Now think about how much time you spend to keep all of these in sync.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not to mention the &lt;strong&gt;cognitive overhead of having to remember&lt;/strong&gt; all the different places where you've defined the same thing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've personally torn my hair out over this a few times. I'd updated the types, input validation, edited my back-end code, the database model and the form, but forgot to update a GraphQL query and schema.&lt;/p&gt;

&lt;p&gt;It can be a nightmare.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now think of what it would be like if you could define your data structures in one place.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;And have everything else derive from that.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Ecosystem Shoutouts
&lt;/h2&gt;

&lt;p&gt;This is where the Zod ecosystem comes in:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need to build APIs from zod schemas?&lt;/strong&gt; 🤔&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tRPC&lt;/code&gt;: end-to-end typesafe APIs without GraphQL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@anatine/zod-nestjs&lt;/code&gt;: using Zod in a NestJS project.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need to integrate zod schemas within your form library?&lt;/strong&gt; 🤔&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;react-hook-form&lt;/code&gt;: Zod resolver for React Hook Form.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zod-formik-adapter&lt;/code&gt;: Formik adapter for Zod.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need to transform zod schemas into other formats?&lt;/strong&gt; 🤔&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;zod-to-json-schema&lt;/code&gt;: Convert Zod schemas into &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schemas&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@anatine/zod-openapi&lt;/code&gt;: Converts Zod schema to OpenAPI v3.x &lt;code&gt;SchemaObject&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nestjs-graphql-zod&lt;/code&gt;: Generates NestJS GraphQL model&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;For each example here, there are at least 4 to 5 more tools and libraries in the Zod ecosystem to choose from.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;✅ Using just the ecosystem alone, you could already use Zod as a single source of truth for any data structure in your codebase.&lt;/p&gt;

&lt;p&gt;🤔 &lt;strong&gt;The problem is that it is quite fragmented.&lt;/strong&gt; Between these different installable tools, there are also different opinions on what an ideal API around it should look like.&lt;/p&gt;

&lt;p&gt;Sometimes, the best code is the code you own yourself. Which is what I've been experimenting with:&lt;/p&gt;




&lt;h2&gt;
  
  
  What makes a good Single Source of Truth?
&lt;/h2&gt;




&lt;p&gt;Let's think about it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What essential metadata should be derivable from a single source of truth?&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IdealSourceOfTruth&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;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&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="c1"&gt;// -i- We need some basis to map to other formats&lt;/span&gt;
  &lt;span class="na"&gt;baseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&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="s1"&gt;Object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
  &lt;span class="na"&gt;zodType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ZodString&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ZodBoolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&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="s1"&gt;ZodObject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;

  &lt;span class="c1"&gt;// -i- We should keep track of optionality&lt;/span&gt;
  &lt;span class="nx"&gt;isOptional&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isNullable&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;defaultValue&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="c1"&gt;// -i- Ideally, for documentation &amp;amp; e.g. GraphQL codegen&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;exampleValue&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;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// -i- If specified...&lt;/span&gt;
  &lt;span class="nx"&gt;minValue&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxValue&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;exactLength&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;regexPattern&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// -i- We should support nested introspection&lt;/span&gt;
  &lt;span class="c1"&gt;// -i- For e.g. enums, arrays, objects, records, ...&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IdealSourceOfTruth&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;IdealSourceOfTruth&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="c1"&gt;// 👆 which would depend on the main `zodType`&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Introspection &amp;amp; Metadata
&lt;/h1&gt;




&lt;h2&gt;
  
  
  What's missing in Zod?
&lt;/h2&gt;

&lt;p&gt;At the core of a good single source of truth is a good introspection API:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introspection&lt;/strong&gt; is the ability to examine the structure of a schema at runtime to extract all relevant metadata from it and its fields.&lt;/p&gt;

&lt;p&gt;Sadly, Zod doesn't have this out of the box.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There's actually a bunch of issues asking for it:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3ccasg1s99d7grrfsjsz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3ccasg1s99d7grrfsjsz.png" alt="Screenshot of multiple issues in the Zod repo asking for a strong introspection API" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems like people really want a strong introspection API in Zod to build their own custom stuff around.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But what if it did have it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turns out, adding introspection to Zod in a way that feels native to it is not super hard:&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding Introspection to Zod
&lt;/h2&gt;

&lt;p&gt;All it takes is some light additions to the Zod interface:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Editing the prototype of anything is typically dangerous and could lead to bugs or unexpected behavior. While we opted to do it to make it feel native to Zod, it's best to only use it for additions (in moderation), but &lt;em&gt;NEVER&lt;/em&gt; modifications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;schemas.ts&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="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="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ZodType&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;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/* --- Zod type extensions ----------------------------- */&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// -i- Add metadata, example and introspection methods to the ZodObject type&lt;/span&gt;
    &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ZodType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Retrieve metadata from a Zod type&lt;/span&gt;
        &lt;span class="nf"&gt;addMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;this&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Add metadata to a Zod type&lt;/span&gt;
        &lt;span class="nx"&gt;example&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_type&lt;/span&gt;&lt;span class="dl"&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;exampleValue&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="k"&gt;this&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Add an example value&lt;/span&gt;
        &lt;span class="nf"&gt;introspect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Introspect a Zod type&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// -i- Make sure we can name and rename Zod schemas&lt;/span&gt;
    &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;nameSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Name a Zod schema&lt;/span&gt;
        &lt;span class="nf"&gt;extendSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Extend a Zod schema &amp;amp; rename it&lt;/span&gt;
        &lt;span class="nf"&gt;pickSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;this&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Pick &amp;amp; rename&lt;/span&gt;
        &lt;span class="nf"&gt;omitSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;this&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Omit &amp;amp; rename&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* --- Zod prototype extensions ------------------------ */&lt;/span&gt;

&lt;span class="c1"&gt;// ... Actual implementation of the added methods, omitted for brevity ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;To check out the full implementation, see the full code on GitHub:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/FullProduct-dev/universal-app-router/blob/with/green-stack/packages/%40green-stack-core/schemas/index.ts" rel="noopener noreferrer"&gt;&lt;strong&gt;FullProduct.dev&lt;/strong&gt; - &lt;strong&gt;@green-stack/core&lt;/strong&gt; - schemas on Github&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Using our custom introspection API
&lt;/h2&gt;

&lt;p&gt;On that note, let's create a custom &lt;code&gt;schema()&lt;/code&gt; function in our custom &lt;code&gt;schemas.ts&lt;/code&gt; file to allow naming and describing single sources of truth without editing the &lt;code&gt;z.object()&lt;/code&gt; constructor directly:&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;// -i- To be used to create the initial schema&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;schema&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;S&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;ZodRawShape&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&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;return&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="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;nameSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// -i- Reexport `z` so the user can opt in / out to prototype extensions&lt;/span&gt;
&lt;span class="c1"&gt;// -i- ...depending on where they import from&lt;/span&gt;
&lt;span class="k"&gt;export&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="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which will allows us to do things like:&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;// -i- Opt into the introspection API by importing from our own `schemas.ts` file&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;z&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;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// -i- Create a single source of truth by using the custom `schema()` function we made &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&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;name&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;example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;age&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;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;addMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;someKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Some introspection data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;birthdate&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;date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The user's birthdate&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="c1"&gt;// -i- Alias the inferred type so you only need 1 import&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To then retrieve all metadata from the 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;userDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;introspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resulting object is a JS object, but could be stringified to JSON if required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;actual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Zod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;used&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;transform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;formats&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"exampleValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodNumber"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"exampleValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;docs&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"someKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Some metadata"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Custom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;meta&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"birthdate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodDate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The user's birthdate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;docs&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later we'll look at how we can use this metadata to generate other things like documentation, mocks, database models, etc.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optionality and Defaults
&lt;/h2&gt;

&lt;p&gt;Zod has native support for things like &lt;code&gt;.nullable()&lt;/code&gt;, &lt;code&gt;.optional()&lt;/code&gt; and &lt;code&gt;.default()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ideally, we'd be able to derive this information in introspection as well.&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;// Define a schema with optional and nullable fields&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&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;name&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;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Allow `undefined`&lt;/span&gt;
    &lt;span class="na"&gt;age&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;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Allow `null`&lt;/span&gt;
    &lt;span class="na"&gt;birthdate&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;date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullish&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Allow `null` &amp;amp; `undefined`&lt;/span&gt;
    &lt;span class="na"&gt;isAdmin&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;boolean&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Allow `undefined` + use `false` if missing&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is actually one of the things that make adding a proper introspection API a bit difficult, since Zod wraps it's internal classes in some layers of abstraction:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// e.g. A nullish field with defaults might look like this:&lt;/span&gt;
&lt;span class="nc"&gt;ZodDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;ZodNullable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ZodOptional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;ZodString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'd typically need to do some recursive layer unwrapping to get to the actual field definition of &lt;code&gt;ZodString&lt;/code&gt; in this case.&lt;/p&gt;

&lt;p&gt;Luckily, our custom &lt;code&gt;introspect()&lt;/code&gt; method is set up to handle this for us and flattens the resulting metadata object into a more easily digestible format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;👇&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;As&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wanted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ideal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Single&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Truth&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"isOptional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodNumber"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;👇&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;As&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wanted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ideal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Single&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Truth&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"isNullable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"birthdate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodDate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;👇&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Both&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="err"&gt;able&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`.&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="err"&gt;ish()`&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"isOptional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
            &lt;/span&gt;&lt;span class="nl"&gt;"isNullable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isAdmin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"zodType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ZodBoolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"baseType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"isOptional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;👇&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Also&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;keeps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;track&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"defaultValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we can extract metadata (optionality, defaults, examples, types, etc.) from our schemas, we can use these introspection results to generate other things.&lt;/p&gt;




&lt;h1&gt;
  
  
  Single Source of Truth Examples
&lt;/h1&gt;




&lt;h2&gt;
  
  
  Designing Database Models with Zod
&lt;/h2&gt;

&lt;p&gt;For example, you want to generate a database model from your Zod schema.&lt;/p&gt;

&lt;p&gt;You could build and apply a transformer function that takes the introspection result and generates a Mongoose model from it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;schemaToMongoose.ts&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="c1"&gt;// Conceptual transformer function&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;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createSchemaPlugin&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;@green-stack/schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;// Import mongoose specific stuff&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mongoose&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;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Document&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;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongoose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// --------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;// -i- Conceptual meta/example code, not actual code&lt;/span&gt;
&lt;span class="c1"&gt;// --------------------------------------------------------&lt;/span&gt;

&lt;span class="c1"&gt;// Take a schema as input, infer its type for later use&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;schemaToMongoose&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;S&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;ZodRawShape&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;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&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="c1"&gt;// Create a field builder for metadata aside from the base type&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createMongooseField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mongooseType&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="c1"&gt;// Return a function that builds a Mongoose field from introspection data&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldMeta&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="c1"&gt;// Build the base definition&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongooseField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mongooseType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isOptional&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isNullable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// Handle special cases like enums&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;fieldMeta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zodType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ZodEnum&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;mongooseField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schemaConfig&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="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// Return the Mongoose field definition&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mongooseField&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Build the mongoose schema definition by mapping metadata to Mongoose fields&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongooseSchemaDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSchemaPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;// Feed it the schema metadata from introspection&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;introspect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="c1"&gt;// Map Zod types to Mongoose types&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMongooseField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMongooseField&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="na"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMongooseField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMongooseField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMongooseField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// ... other zod types ...&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Create mongoose Schema from SchemaDefinition&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SchemaDoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;ZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Infer the schema type&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;mongooseSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SchemaDoc&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mongooseSchemaDefinition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Build &amp;amp; return the mongoose model&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schemaModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SchemaDoc&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;schemaName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mongooseSchema&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;SchemaModel&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;schemaModel&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;This is a conceptual example. The actual implementation would be a bit (but not much) more complex and handle more edge cases. I'll link you my actual implementations at the end.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To build a Mongoose model from a Zod schema, you'd use it 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="c1"&gt;// Import the schema and the transformer function&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;User&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;./schemas&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;schemaToMongoose&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;./schemaToMongoose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Generate the Mongoose model from the Zod schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;schemaToMongoose&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="c1"&gt;// &amp;lt;- Typed Mongoose model with `User` type&lt;/span&gt;

&lt;span class="c1"&gt;// Use the Mongoose model as you would any other&lt;/span&gt;
&lt;span class="c1"&gt;// It will apply &amp;amp; enforce the types inferred from the Zod schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&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;UserModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;birthdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1980-01-01&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 could apply the same principle to generate other database modeling tools or ORMs like Prisma, TypeORM, Drizzle etc.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build a transformer function that takes in a schema&lt;/li&gt;
&lt;li&gt;Use introspection to extract metadata from the schema&lt;/li&gt;
&lt;li&gt;Map the metadata to some other structure (like a DB schema)&lt;/li&gt;
&lt;li&gt;Build the full database model from the transformed fields&lt;/li&gt;
&lt;li&gt;Assign the types inferred from the Zod schema to the database model&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Now, if you need to change your database model, you only need to change the Zod schema. Typescript will automatically catch any errors in your codebase that need to be updated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pretty powerful, right?&lt;/p&gt;




&lt;h2&gt;
  
  
  Generating Docs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="" class="article-body-image-wrapper"&gt;&lt;img alt="Documentation drives adoption"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many don't feel they have the time to write good documentation, even though they might see it as important.&lt;/p&gt;

&lt;p&gt;What if you could attach the same Zod schema your React components use for types to generate docs from?&lt;/p&gt;

&lt;p&gt;You'd need to parse the schema metadata and generate a markdown table or something like &lt;strong&gt;Storybook controls&lt;/strong&gt; from it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;schemaToStorybookDocs.ts&lt;/code&gt;&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="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="nx"&gt;schema&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;@green-stack/schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/* --- Prop Schema ----------------- */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GreetingProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GreetingProps&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;name&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;example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&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="cm"&gt;/* --- &amp;lt;Greeting /&amp;gt; ---------------- */&lt;/span&gt;

&lt;span class="c1"&gt;// -i- React component that uses the schema's type inference&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;Greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;GreetingProps&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome back, &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;! 👋&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cm"&gt;/* --- Documentation --------------- */&lt;/span&gt;

&lt;span class="c1"&gt;// -i- Export the schema for Storybook to use&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;docSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GreetingProps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;You'll need some node script to scan your codebase with e.g. glob and build &lt;code&gt;.stories.mdx&lt;/code&gt; files for you though. In those generated markdown files, you'll map&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;schemaToStorybookDocs.ts&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="c1"&gt;// Similiar to the Mongoose example, but for Storybook controls&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;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createSchemaPlugin&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;@green-stack/schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/* --- schemaToStorybookDocs() ----- */&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;schemaToStorybookDocs&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;S&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;ZodRawShape&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;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&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="c1"&gt;// ... Similar conceptual code to the Mongoose example ...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createStorybookControl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;controlType&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;fieldKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldMeta&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="c1"&gt;// ... Do stuff with metadata: defaultValue, exampleValue, isOptional, enum options etc ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Build the Storybook controls definition by mapping metadata to Storybook controls&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storybookControls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSchemaPlugin&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="nf"&gt;introspect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Map baseTypes to Storybook data (👇) &amp;amp; control (👇) types&lt;/span&gt;
            &lt;span class="na"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createStorybookControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&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="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createStorybookControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&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="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createStorybookControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&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="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createStorybookControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&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="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createStorybookControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enum&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="s1"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- e.g. Use a select dropdown for enums&lt;/span&gt;
            &lt;span class="c1"&gt;// ... other zod types ...&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Return the Storybook controls&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;storybookControls&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which, after codegen creates a &lt;code&gt;.stories.mdx&lt;/code&gt; file that uses &lt;code&gt;schemaToStorybookDocs()&lt;/code&gt;, might look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fz5lqp93i312z5ipa6qv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fz5lqp93i312z5ipa6qv6.png" alt="Storybook example with controls generated from a Zod schema" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Demo:&lt;/strong&gt; &lt;em&gt;You can test out a full working example of Zod-powered Storybook docs here: &lt;a href="https://main--63e8ae7f443d84f16518d4e5.chromatic.com/?path=/docs/packages-aetherspace-components-aethericon--aether-icon" rel="noopener noreferrer"&gt;codinsonn.dev fully generated Storybook docs&lt;/a&gt; (+ &lt;a href="https://github.com/codinsonn/codinsonn.dev" rel="noopener noreferrer"&gt;Github Source&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Resolvers and Databridges
&lt;/h2&gt;




&lt;p&gt;My favorite example of building stuff around schemas is a concept I've dubbed a &lt;code&gt;DataBridge&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can think of a &lt;code&gt;DataBridge&lt;/code&gt; as a way to bundle the metadata around a resolver function with the Zod schemas of its input and output.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, if we have a resolver function that works in both REST or GraphQL :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;healthCheck.bridge.ts&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="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="nx"&gt;schema&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;@green-stack/core/schemas&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;createDataBridge&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;@green-stack/core/schemas/createDataBridge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/* --- Schemas ----------------------------------------- */&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;HealthCheckArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HealthCheckArgs&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;echo&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;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Echoes back the echo argument&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="c1"&gt;// Since we reuse the "echo" arg in the response, we can extend (👇) from the input schema&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;HealthCheckResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HealthCheckArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extendSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HealthCheckResponse&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;alive&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;boolean&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;kicking&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;boolean&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="cm"&gt;/* --- Types ------------------------------------------- */&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HealthCheckArgs&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;HealthCheckArgs&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HealthCheckResponse&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="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;HealthCheckResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;/* --- DataBridge -------------------------------------- */&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;healthCheckBridge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDataBridge&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Bundles the input &amp;amp; output schemas with the resolver metadata&lt;/span&gt;
    &lt;span class="na"&gt;argsSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HealthCheckArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;responseSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HealthCheckResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// API route metadata&lt;/span&gt;
    &lt;span class="na"&gt;apiPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;allowedMethods&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="s1"&gt;GET&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="s1"&gt;POST&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="s1"&gt;GRAPHQL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// GraphQL metadata&lt;/span&gt;
    &lt;span class="na"&gt;resolverName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;healthCheck&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 might wonder why we're defining this in a file that's separate from the actual resolver function.&lt;/p&gt;

&lt;p&gt;The reason is that we can reuse this &lt;code&gt;DataBridge&lt;/code&gt; on both the client and server side.&lt;/p&gt;

&lt;p&gt;On the server side, image a wrapper &lt;code&gt;createResolver()&lt;/code&gt; function that takes a function implementation as a first argument and the &lt;code&gt;DataBridge&lt;/code&gt; as a second:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;healthCheck.resolver.ts&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="c1"&gt;// Helper function that integrates the resolver with the DataBridge&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;createResolver&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;@green-stack/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Import the DataBridge we defined earlier&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;healthCheckBridge&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;./healthCheck.bridge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/* --- healthCheck() ----------------------------------- */&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;healthCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createResolver&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;parseArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withDefaults&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="c1"&gt;//                               Handy helpers (☝️) from the DataBridge&lt;/span&gt;

    &lt;span class="c1"&gt;// Typesafe Args from Databridge Input schema&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;echo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- { echo?: string }&lt;/span&gt;
    &lt;span class="c1"&gt;// -- OR --&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;echo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseArgs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- { echo?: string }&lt;/span&gt;

    &lt;span class="c1"&gt;// Check type match from the Databridge Output schema &amp;amp; apply defaults&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;withDefaults&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Caught by Typescript, will have red squiggly line&lt;/span&gt;
        &lt;span class="na"&gt;kicking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Defaults to `true`&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;healthCheckBridge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ☝️ Pass the DataBridge as the second argument to power types &amp;amp; resolver utils&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congrats. On the server side, you now have a fully typed resolver function that's bundled together with schemas of its input and output.&lt;/p&gt;

&lt;p&gt;On the client, you could use just the DataBridge to build a REST fetcher or even build a GraphQL query from the bridge object, without conflicting with the server-side code.&lt;/p&gt;

&lt;p&gt;While on the server, you could use the portable resolver bundle to generate your executable GraphQL schema from. Automagically.&lt;/p&gt;

&lt;p&gt;Let's have a look at how that might be achieved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Zod for simplifying GraphQL
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Who thinks GraphQL is too complicated? 🙋‍♂    &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's bring our &lt;code&gt;healthCheck&lt;/code&gt; resolver to GraphQL. We'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate GraphQL schema definitions from the &lt;code&gt;healthCheckBridge&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generate a GraphQL query to call the &lt;code&gt;healthCheckBridge&lt;/code&gt; query from the front-end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, we'll combine the introspection API for the &lt;code&gt;DataBridge&lt;/code&gt; with a transformer function to generate the GraphQL schema and query:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bridgeToSchema.ts&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="c1"&gt;// Similiar to the Mongoose &amp;amp; Docs example, but for a GraphQL-schema&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;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createSchemaPlugin&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;@green-stack/core/schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="c1"&gt;// -i- We'll need to run this for both the Args &amp;amp; Response schemas individually&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storybookControls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSchemaPlugin&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="nf"&gt;introspect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Map baseTypes to GraphQL-schema definitions&lt;/span&gt;
        &lt;span class="na"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createSchemaField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createSchemaField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createSchemaField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Float&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- e.g. Float! or Float&lt;/span&gt;
        &lt;span class="na"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createSchemaField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- e.g. Date! or Date (scalar)&lt;/span&gt;
        &lt;span class="na"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createSchemaField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- e.g. String! or String&lt;/span&gt;
        &lt;span class="c1"&gt;// ... other zod types ...&lt;/span&gt;
        &lt;span class="na"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createSchemaField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schemaName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Will need some recursion magic for nested schemas&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;Once applied and loaded into your GraphQL server, if your mapper function is set up correctly, you should be able to propagate even your descriptions from &lt;code&gt;z.{someType}.describe('...')&lt;/code&gt; to your GraphQL schema:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fll1pieh8ywjqoroanek8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fll1pieh8ywjqoroanek8.jpeg" alt="GraphQL-schema example generated from our healthCheck example DataBridge" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now no longer need to maintain separate GraphQL schema definitions.&lt;br&gt;&lt;br&gt;
It's all derived from the Zod args and response schemas in the resolver's &lt;code&gt;DataBridge&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With a bit of creativity, we could even generate the GraphQL query to call the &lt;code&gt;healthCheck&lt;/code&gt; resolver from the front-end:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;healthCheck.query.ts&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="c1"&gt;// -i- Like types &amp;amp; schemas, we can import &amp;amp; reuse the bridge client-side&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;healthCheckBridge&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;./healthCheck.bridge&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;renderBridgeToQuery&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;@green-stack/schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// -i- Generate a GraphQL query from the DataBridge&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;healthCheckQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderBridgeToQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;healthCheckBridge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cm"&gt;/* -i- Resulting in the following query string:

    `query($args: HealthCheckArgs) {
        healthCheck(args: $args) {
            echo
            alive
            kicking
        }
    }`
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which you could then use to build a typed fetcher function:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;healthCheck.fetcher.ts&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;healthCheckFetcher&lt;/span&gt; &lt;span class="o"&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;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;healthCheckBridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argsSchema&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="c1"&gt;// -i- Fetch the query with the args (inferred from the schema ☝️)&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/graphql&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;headers&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="s1"&gt;Content-Type&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="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// ...headers,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&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="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;healthCheckQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;variables&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// -i- Return the response (which you can type from the response schema 👇)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&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;healthCheck&lt;/span&gt; &lt;span class="k"&gt;as&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;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;healthCheckBridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseSchema&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;blockquote&gt;
&lt;p&gt;In the end, you get all the benefits of GraphQL while avoiding most extra boilerplate steps involved in maintaining it&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scaffolding Forms?
&lt;/h2&gt;

&lt;p&gt;As a final example, many only think of validation libraries like Zod for integrating them with their forms.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But what if you could scaffold your forms from your Zod schemas on top?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;UserRegistrationForm.tsx&lt;/code&gt;&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SchemaForm&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

        &lt;span class="cm"&gt;/* With single sources of truth, */&lt;/span&gt;
        &lt;span class="cm"&gt;/* ...what's stopping you from coding like this? */&lt;/span&gt;

        &lt;span class="na"&gt;schemaToInputs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        }}
    /&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Even better DX with Codegen
&lt;/h2&gt;




&lt;p&gt;All of this might still seem like a lot of manual linking between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zod schemas&lt;/li&gt;
&lt;li&gt;Databridges &amp;amp; Resolvers&lt;/li&gt;
&lt;li&gt;Forms, Hooks, Components, Docs, APIs, Fetchers, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;But even this can be automated with cli tools if you want it to be:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; Modify &lt;span class="s2"&gt;"your-codebase"&lt;/span&gt; using turborepo generators?

? Where would you like to add this schema? &lt;span class="c"&gt;# -&amp;gt; @app/core/schemas&lt;/span&gt;
? What will you name the schema? &lt;span class="o"&gt;(&lt;/span&gt;e.g. &lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# -&amp;gt; UserSchema&lt;/span&gt;
? Optional description: What will this schema be used &lt;span class="k"&gt;for&lt;/span&gt;? &lt;span class="c"&gt;# -&amp;gt; Keep track of user data&lt;/span&gt;
? What would you like to generate linked to this resolver?

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ✅ Database Model &lt;span class="o"&gt;(&lt;/span&gt;Mongoose&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ✅ Databridge &amp;amp; Resolver shell
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ✅ GraphQL Query / Mutation
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ✅ GET / POST / PUT / DELETE API routes
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ✅ Typed fetcher &lt;span class="k"&gt;function&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ✅ Typed &lt;span class="sb"&gt;`&lt;/span&gt;formState&lt;span class="sb"&gt;`&lt;/span&gt; hook
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ✅ Component Docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sweet thing is, you don't need to build this all from scratch anymore...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While there's no NPM package for this,  where can you test working with Single Sources of Truth?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  FullProduct.dev ⚡️ Universal App Starterkit
&lt;/h2&gt;




&lt;p&gt;&lt;a href="https://media2.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%2F36j37v1d89excbmhhevo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F36j37v1d89excbmhhevo.png" alt="Banner Image showing FullProduct.dev logo in Love + Death + Robots style graphic next to the Zod logo" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been working on a project called &lt;a href="https://fullproduct.dev" rel="noopener noreferrer"&gt;FullProduct.dev&lt;/a&gt; to provide a full-stack starterkit for building modern Universal Apps. Single sources of truth are a big part of that.&lt;/p&gt;

&lt;h4&gt;
  
  
  Like most time-saving templates, it will set you up with:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Scalable Back-end&lt;/li&gt;
&lt;li&gt;Essential UI components&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ But those other boilerplates are usually &lt;em&gt;just for the web&lt;/em&gt;, and &lt;em&gt;often don't have extensive docs&lt;/em&gt; or an &lt;em&gt;optional recommended way of working&lt;/em&gt; that comes with them.&lt;/p&gt;

&lt;p&gt;🤔 You also often don't get to test the template before you buy it, and &lt;em&gt;might still have to spend time switching out parts you don't like&lt;/em&gt; with ones you're used to.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why FullProduct.dev ⚡️ ?
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Universal from the start&lt;/strong&gt; - Bridges gap between &lt;code&gt;Expo&lt;/code&gt; &amp;amp; &lt;code&gt;Next.js&lt;/code&gt;&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Write-once UI&lt;/strong&gt; - Combines &lt;code&gt;NativeWind&lt;/code&gt; &amp;amp; &lt;code&gt;React Native Web&lt;/code&gt; for consistent look &amp;amp; feel on each device&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Recommended way of working&lt;/strong&gt; - based on &lt;code&gt;Schemas&lt;/code&gt;, &lt;code&gt;DataBridges&lt;/code&gt; &amp;amp; Single Sources of Truth&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Docs and Docgen&lt;/strong&gt; - Documentation that grows with you as you continue to build with schemas&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Built for Copy-Paste&lt;/strong&gt; - Our way of working enables you to copy-paste full features between projects&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Customizable&lt;/strong&gt; - Pick and choose from &lt;em&gt;inspectable &amp;amp; diffable&lt;/em&gt; &lt;code&gt;git-based plugins&lt;/code&gt;  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While &lt;strong&gt;FullProduct.dev is still in active development,&lt;/strong&gt; &lt;em&gt;scheduled for a Product Hunt release in September 2024&lt;/em&gt;, you can already explore its core concepts in the source-available &lt;strong&gt;free demo&lt;/strong&gt; or the &lt;strong&gt;previous iteration&lt;/strong&gt;:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/FullProduct-dev/green-stack-starter-demo#readme" rel="noopener noreferrer"&gt;&lt;strong&gt;FullProduct.dev GREEN stack starter&lt;/strong&gt;&lt;/a&gt; - Previous iteration of FullProduct.dev (&lt;a href="https://main--62c9a236ee16e6611d719e94.chromatic.com/?path=/story/aetherspace-quickstart--page" rel="noopener noreferrer"&gt;docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/FullProduct-dev/universal-app-router" rel="noopener noreferrer"&gt;&lt;strong&gt;Universal Base Starter&lt;/strong&gt;&lt;/a&gt; - Base FullProduct.dev starterkit, with plugin PRs (&lt;a href="https://universal-base-starter-docs.vercel.app/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa8srwyr6o9kn3l57fu8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa8srwyr6o9kn3l57fu8t.png" alt="Universal Base Starter docs screenshot" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The full working versions of the pseudo-code examples can also be found in these template repos:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To get started with them, use the Github UI to fork it and include all branches&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpn0fppj7n6242amrbojm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpn0fppj7n6242amrbojm.png" alt="Image describing how to use Github UI to fork the template repo" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to check what our &lt;code&gt;git-based plugins&lt;/code&gt; might feel like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flnrsbpswt32xtxje549f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flnrsbpswt32xtxje549f.png" alt="Image describing how to use Github UI to include all branches" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Liked this article? 💚
&lt;/h2&gt;




&lt;p&gt;Want to get regular updates on the &lt;code&gt;FullProduct.dev ⚡️&lt;/code&gt; project?&lt;br&gt;&lt;br&gt;
Or learn more about single sources of truth, data bridges or Zod?&lt;/p&gt;

&lt;p&gt;You can find the links to the source code / blog / socials on &lt;a href="https://codinsonn.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;codinsonn.dev&lt;/strong&gt;&lt;/a&gt; ⚡️&lt;/p&gt;

&lt;p&gt;Thank you for reading, hope you found it inspirational! 🙏&lt;/p&gt;

</description>
      <category>zod</category>
      <category>codegen</category>
      <category>codequality</category>
      <category>typescript</category>
    </item>
    <item>
      <title>React-Native over Flutter? - How to choose cross-platform tech</title>
      <dc:creator>Thorr ⚡️ codinsonn.dev</dc:creator>
      <pubDate>Thu, 24 Aug 2023 16:04:26 +0000</pubDate>
      <link>https://dev.to/codinsonn/why-use-react-native-over-flutter-a-recap-57b0</link>
      <guid>https://dev.to/codinsonn/why-use-react-native-over-flutter-a-recap-57b0</guid>
      <description>&lt;p&gt;Users increasingly prefer mobile apps to the mobile web, and there are many ways to build them.&lt;/p&gt;

&lt;p&gt;Recently, 'VeryGoodVentures' (a Flutter Consultancy Company) released a &lt;a href="https://verygood.ventures/whitepaper/business-value-of-flutter" rel="noopener noreferrer"&gt;whitepaper&lt;/a&gt; detailing stats on the impact of adaptating Flutter.&lt;/p&gt;

&lt;p&gt;One very true take the Whitepaper starts with is that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqkrvcbeqh66j78rrk1wh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqkrvcbeqh66j78rrk1wh.png" alt="Screenshot reading 'To reach your customers in most markets, you need to offer Android and iOS apps, and also a Web browser based experience'" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which, ofcourse, sparked the age old debate of what cross-platform technology is best to start with. A discussion where the main options are the usual suspects of Flutter, React-Native and web wrappers like Cordova/Ionic/Capacitor.&lt;/p&gt;

&lt;p&gt;This blogpost examines when each of these solutions tends to make sense. As with most tech choices, the answer is "It depends"&lt;/p&gt;

&lt;h2&gt;
  
  
  Misleading stats?
&lt;/h2&gt;

&lt;p&gt;You might notice that in their promotional material of their whitepaper, the graph they share comparing Flutter to React-Native looks kind of funky:&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React-Native is at 42% in the top left corner&lt;/li&gt;
&lt;li&gt;at the same elevation in the opposite corner it states 46%?&lt;/li&gt;
&lt;li&gt;in the middle, it also states 42% (??)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that, &lt;a href="https://www.reddit.com/r/FlutterDev/comments/yfeoyo/whats_going_on_with_the_job_market_for_flutter/" rel="noopener noreferrer"&gt;other sources&lt;/a&gt; say that it is actually pretty hard to find a job in Flutter these days. Which would contradict some of the statements made in the whitepaper and the graph they chose to share.&lt;/p&gt;

&lt;p&gt;These could ofcourse just be design mistakes. But, let's have a look at some important, easily verifyable numbers instead:&lt;/p&gt;

&lt;h2&gt;
  
  
  What the App Stores say
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://expo.github.io/router/docs" rel="noopener noreferrer"&gt;Expo-Router&lt;/a&gt; maintainer &lt;a class="mentioned-user" href="https://dev.to/evanbacon"&gt;@evanbacon&lt;/a&gt; (&lt;a href="https://twitter.com/Baconbrix" rel="noopener noreferrer"&gt;@baconbrix&lt;/a&gt; on Twitter) does a great job comparing what tech is used under the hood in the top 100 apps of various app store categories:&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;From these app store comparisons, you could conclude the following things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Percentage wise, a lot of the top 100 apps in any category still seem to be built with either native tech (like Swift or Kotlin) or a bit more outdated &lt;a href="https://twitter.com/Baconbrix/status/1694754819548827871" rel="noopener noreferrer"&gt;native adjacent interface builders&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;React-Native is a close second, with pretty good ratings overall&lt;/li&gt;
&lt;li&gt;Flutter &amp;amp; Web wrappers are definitely represented, but not nearly as much as React-Native and Native are.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But, is this really a fair comparison? Do these categories cover all the areas and types of apps where you could consider Flutter?&lt;/p&gt;

&lt;h2&gt;
  
  
  So when does Flutter make sense?
&lt;/h2&gt;

&lt;p&gt;Where Flutter really shines is when animation and graphics performance matters a lot.&lt;/p&gt;

&lt;p&gt;So, if we were to instead compare the top 100 image / video editing tools or top 100 gaming apps, I would not be surprised if Flutter took more of a lead there. (Where react-native and web wrappers are probably not even represented at all)&lt;/p&gt;

&lt;p&gt;The reasons are as follows:&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose cross-platform tech?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://youtu.be/tTGWfXPKxf4" rel="noopener noreferrer"&gt;one of his recent videos&lt;/a&gt;, twitch streamer and open-source maintainer Theo Browne (&lt;a class="mentioned-user" href="https://dev.to/t3dotgg"&gt;@t3dotgg&lt;/a&gt;) came up with a pretty good decision framework for when to choose which tech for the solution your app solves:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpuxucjwx46rj2abxhrpv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpuxucjwx46rj2abxhrpv.png" alt="Graph depicting a y-axis of animation needed and x-axis of native features required" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When examining which tech stack to use for mobile dev, consider the choices based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much animation your app will need (Y axis)&lt;/li&gt;
&lt;li&gt;How many native features the app needs (X axis)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And choose + hire accordingly.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowing this, you should choose Flutter if:
&lt;/h3&gt;

&lt;p&gt;✅ You have considered using an actual game engine instead&lt;br&gt;
✅ You're building a heavily gamified app (e.g. for kids to learn stuff in)&lt;br&gt;
✅ You're building an editing tool like the next Canva or Figma&lt;br&gt;
✅ You know upfront that web won't need any SSR or SEO&lt;/p&gt;

&lt;p&gt;Which would correspond pretty well with the pink line from Theo's video.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(PS: I highly recommend watching &lt;a href="https://youtu.be/tTGWfXPKxf4" rel="noopener noreferrer"&gt;the entire video&lt;/a&gt; and Theo's &lt;a href="https://youtu.be/3_FcxGCCnUs" rel="noopener noreferrer"&gt;other&lt;/a&gt; &lt;a href="https://youtu.be/ZM8lJIJb2Q4" rel="noopener noreferrer"&gt;ones&lt;/a&gt; on this topic as well)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Though a canvas-based implementation in Flutter Web typically hinders its usefulness in terms of SEO, it * has * proven to be a great asset in web-only tools like &lt;a href="https://www.chili-publish.com/" rel="noopener noreferrer"&gt;CHILI Publish&lt;/a&gt;, where  the surrounding UI is largely made in React, but the core image &amp;amp; animation editing features are fully built in Flutter for Web.&lt;/p&gt;

&lt;h2&gt;
  
  
  When web, SEO &amp;amp; SSR make sense
&lt;/h2&gt;

&lt;p&gt;The best way to get free organic traffic to your solution is still by building a website or even a web version of your app.&lt;/p&gt;

&lt;p&gt;Even in the age of AI, people still search for solutions to their problems in search engines like Google.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure you're in their search results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is not to say that you should * only * build a website or webapp. Because &lt;a href="https://youtu.be/xu9KbSOKy2M" rel="noopener noreferrer"&gt;research&lt;/a&gt; also shows that users increasingly prefer mobile apps when they're already hooked on your solution.&lt;/p&gt;

&lt;p&gt;So, in essence, web for discovery, and mobile apps for conversions and reach. You'll likely need both.&lt;/p&gt;

&lt;p&gt;One way you could achieve that easily is by using web-wrappers like Cordova/Ionic/Capacitor.&lt;/p&gt;

&lt;p&gt;According to the tech decision framework, here's when that works best:&lt;/p&gt;

&lt;h3&gt;
  
  
  When might one use web wrappers for mobile?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Feejptnvngheexueiso6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Feejptnvngheexueiso6d.png" alt="Updated graph showing area where mobile wrappers make most sense" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ You want a native-like experience with just web-tech&lt;br&gt;
✅ ... but the results don't need to be an actual native app&lt;br&gt;
✅ Team knows only non React JS frameworks like Vue or Svelte&lt;br&gt;
✅ e.g. You're building an eCommerce store, a blogging platform (SEO)&lt;/p&gt;

&lt;p&gt;It's also important to know that like with React-Native, tools like Capacitor can actually help you implement integrations with actual native features and even custom native code should you want to.&lt;/p&gt;

&lt;p&gt;While with React-Native, this can also go both ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use tools like React-Native and Expo?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fi4dzjty26k6mr3cayd87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fi4dzjty26k6mr3cayd87.png" alt="Updated graph showing area where React-Native makes most sense" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ You want an actual native experience using JS &amp;amp; React&lt;br&gt;
✅ You might need to implement native code at some point&lt;br&gt;
✅ Team already knows React and wants to take those skills cross-platform&lt;br&gt;
✅ You want file-based routing with automatic deeplinks (Expo Router)&lt;br&gt;
✅ Your app is not blasted with cutting edge native features&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One important caveat and requirement for the web though:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;✅ Your third party libraries are compatible with React-Native-Web&lt;/p&gt;

&lt;p&gt;While the react-native area is pretty big compared to the others, this might be the most limiting factor depending on the complexity of what you're doing for web.&lt;/p&gt;

&lt;p&gt;Expo and the React open-source community are putting in tons of work to bring most react-native features to web as well, but it's probably safe to assume that only 60 - 80% will be actually compatible with tools like Expo for Web or Expo's Next.js adapter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F82le54f2dwomymztwjmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F82le54f2dwomymztwjmc.png" alt="What's supported?" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Personal advice when considering switching from web to React-Native: Try building an MVP with your most important react libraries first. If those work, great! If they don't, are there alternatives of equal quality available that do?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Supporting web with React-Native and Expo
&lt;/h2&gt;

&lt;p&gt;The most straight forward way to support Web when choosing React-Native would be to use Expo and its static web output.&lt;/p&gt;

&lt;p&gt;However, while they provide an API to get static data at build time, this does not really support frequently updating data like a much more flexible Next.js project would allow for. The API's where you'd get your data from would also need to have their own separate server since there's no API routes when using Expo for Web.&lt;/p&gt;

&lt;p&gt;Instead, you might be better off using an Expo + Next.js starter:&lt;/p&gt;

&lt;h3&gt;
  
  
  When to use an Expo + Next.js starter?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxqb87hha7gc8nyt81ajx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxqb87hha7gc8nyt81ajx.png" alt="Image detailing Tamagui, Solito and FullProduct.dev" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(Since Tamagui uses Solito &amp;amp; both FullProduct.dev and Solito can use Tamagui, I've decided to clump them together for this section) as Expo + Next.js starters:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;✅ You want to use Expo + Next.js and don't want to set that up yourself&lt;br&gt;
✅ You want to show an interactive demo of the app on the landing page&lt;br&gt;
✅ Which might mean you need web UI optimized at build time (tamagui)&lt;br&gt;
✅ You're not averse to using a monorepo, or using Next.js API routes&lt;/p&gt;

&lt;p&gt;Whichever starter you choose, the fact they all use Next.js also means you get the benefits of easier optimization for web-vitals, which is a must to rank high in search engines.&lt;/p&gt;

&lt;p&gt;So where do their differences lie?&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use which opinionated starter?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;So, this is the part where I reveal my bias as the maintainer of &lt;a href="https://fullproduct.dev" rel="noopener noreferrer"&gt;FullProduct.dev&lt;/a&gt;. Which doesn't mean that I don't also see the usecase for using Tamagui, their newly released premium starter called Takeout or another powerful Solito based starter like T4. Your choice will likely come down to personal preference.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Honestly, all will set you up with a universal app:&lt;/p&gt;

&lt;p&gt;📱 Expo with Expo-Router for iOS and Android&lt;br&gt;
🖥 Next.js with the app-dir for Browsers, Servers &amp;amp; API&lt;br&gt;
⛓ Deeplinking between Web &amp;amp; Mobile&lt;br&gt;
🚀 Write-once, render &amp;amp; deploy anywhere&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The true difference lies in the way of working.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you enjoy:&lt;/p&gt;

&lt;p&gt;✅ a single cli command to get set-up (&lt;a href="https://tamagui.dev/docs/guides/create-tamagui-app" rel="noopener noreferrer"&gt;create-tamagui-app&lt;/a&gt;)&lt;br&gt;
✅ using packages to share code between related software&lt;br&gt;
✅ spending a little more time to fully own your setup&lt;br&gt;
✅ a cool GitHub bot that helps you merge new updates into your project&lt;br&gt;
✅ a more styling &amp;amp; theming focused solution&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Then &lt;a href="https://tamagui.dev/takeout" rel="noopener noreferrer"&gt;Tamagui / Takeout&lt;/a&gt; is likely the solution for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But if you're more into:&lt;/p&gt;

&lt;p&gt;✅ Tailwind for styling both Web and Mobile&lt;br&gt;
✅ A starter you can personalize so it grows with you&lt;br&gt;
✅ Template Repos and a git-based plugin branch system&lt;br&gt;
✅ The option to still add tRPC and Tamagui later on&lt;br&gt;
✅ A setup designed for copy-paste&lt;/p&gt;

&lt;p&gt;as well as&lt;/p&gt;

&lt;p&gt;✅ Using &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;zod.dev&lt;/a&gt; for Single sources of truth (Types, GraphQL, Stories, DB)&lt;br&gt;
✅ Both GraphQL &amp;amp; REST APIs by writing data resolvers once&lt;br&gt;
✅ Autogenerated docs from zod &amp;amp; the filesystem&lt;br&gt;
✅ A way of working that promotes organizing for copy-paste&lt;/p&gt;

&lt;p&gt;and overall:&lt;/p&gt;

&lt;p&gt;✅ A pretty scalable setup for your startup without the time investment&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Then you might want to check out &lt;a href="https://fullproduct.dev?v=dev-to" rel="noopener noreferrer"&gt;FullProduct.dev&lt;/a&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Thanks for reading!
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you liked this writeup, you may also like my article on how your startup can use cross-platform tech to compete with the Elon Musks of the world:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://dev.to/codinsonn/how-to-compete-with-elons-twitter-a-dev-perspective-4j64"&gt;https://dev.to/codinsonn/how-to-compete-with-elons-twitter-a-dev-perspective-4j64&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a quick intro to that post:&lt;/p&gt;




&lt;h1&gt;
  
  
  'Move fast &amp;amp; build things' with Zod, Expo &amp;amp; Next.js
&lt;/h1&gt;

&lt;p&gt;Musk’s takeover of twitter had a lot of devs looking to swoop in to build the next best thing.&lt;/p&gt;

&lt;p&gt;While it isn’t possible to create a new twitter app in just a few weeks ... &lt;strong&gt;there ARE ways to gain enough speed to compete&lt;/strong&gt; with Elon's version of twitter.&lt;/p&gt;

&lt;p&gt;I recently delved into this during my first tech talk, titled &lt;strong&gt;"Move fast &amp;amp; build things"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/posts/thorr-codinsonn-dev-6951361b7_reactnative-elonmuskmemes-techtalks-activity-6996373531961782272-RRNC?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2F3a3suihgfmgpc43s68i4.png" alt="Picture of me giving my talk, title slide being shown" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll re-share the tips &amp;amp; tricks discussed during the talk here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ How to &lt;strong&gt;build for iOS, Android and the web&lt;/strong&gt;, from the start, in a &lt;strong&gt;write-once&lt;/strong&gt; way. (and why you should)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ How to scale with ease, &lt;strong&gt;Automate your documentation with Storybook&lt;/strong&gt;, and focus on building your features well and fast instead.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Learn more through &lt;a href="https://codinsonn.dev" rel="noopener noreferrer"&gt;codinsonn.dev&lt;/a&gt; or &lt;a href="https://dev.to/codinsonn/how-to-compete-with-elons-twitter-a-dev-perspective-4j64"&gt;continue reading on DEV.to&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>crossplatform</category>
      <category>startup</category>
      <category>flutter</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>'Move fast &amp; build things' with Zod, Expo &amp; Next.js</title>
      <dc:creator>Thorr ⚡️ codinsonn.dev</dc:creator>
      <pubDate>Sun, 13 Nov 2022 21:59:21 +0000</pubDate>
      <link>https://dev.to/codinsonn/how-to-compete-with-elons-twitter-a-dev-perspective-4j64</link>
      <guid>https://dev.to/codinsonn/how-to-compete-with-elons-twitter-a-dev-perspective-4j64</guid>
      <description>&lt;p&gt;Musk’s takeover of Twitter had a lot of devs looking to swoop in to build the next best thing.&lt;/p&gt;

&lt;p&gt;While it isn’t possible to create a new Twitter app in just a few weeks ... &lt;strong&gt;there ARE ways to gain enough speed to compete&lt;/strong&gt; with Elon's version of Twitter.&lt;/p&gt;

&lt;p&gt;I recently delved into this during my tech talk &lt;strong&gt;"Move fast &amp;amp; build things"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/posts/thorr-codinsonn-dev-6951361b7_reactnative-elonmuskmemes-techtalks-activity-6996373531961782272-RRNC?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2F3a3suihgfmgpc43s68i4.png" alt="Picture of me giving my talk, title slide being shown" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll re-share the tips &amp;amp; tricks discussed during the talk here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ How to &lt;strong&gt;build for iOS, Android and the web&lt;/strong&gt;, from the start, in a &lt;strong&gt;write-once&lt;/strong&gt; way. (and why you should)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ How to scale with ease, &lt;strong&gt;Automate your documentation with Storybook&lt;/strong&gt;, and focus on building your features well and fast instead.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why these specific topics? Well, looking at it through the same lens, let's say you have an app in mind…&lt;/p&gt;

&lt;h2&gt;
  
  
  App idea 💡 New, better Twitter
&lt;/h2&gt;

&lt;p&gt;So, you've chosen to compete with Elon, who is ruining the platform he bought at an alarming rate. But until it &lt;em&gt;IS&lt;/em&gt; ruined, you'll need to offer enough similar features, and Twitter has plenty.&lt;/p&gt;

&lt;p&gt;Meaning, from the start, you already have to optimize for speed. Playing &lt;em&gt;'feature catch-up'&lt;/em&gt; requires us to literally move fast and build things.&lt;/p&gt;

&lt;p&gt;Another thing to consider is Twitter's &lt;strong&gt;reach&lt;/strong&gt;. It is &lt;strong&gt;already available for web, iOS, and Android&lt;/strong&gt;. If you take yourself seriously, you'll need to launch for those platforms as well. &lt;/p&gt;

&lt;p&gt;Building in a &lt;strong&gt;"Hybrid-first"&lt;/strong&gt; way becomes a must.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, if you launched something users actually want to use, but it's only available on the web, you'll eventually get comments like these:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjylrn117z9rcqx0nhpin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjylrn117z9rcqx0nhpin.png" alt="Parody tweet by Elon Musk, taunting us and asking " width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this may just be Elon taunting you as his new competitor, genuinely interested users will be asking exactly the same thing.&lt;/p&gt;

&lt;p&gt;This should not come as a surprise, because users increasingly prefer to use the internet in Mobile apps vs Websites.&lt;/p&gt;

&lt;p&gt;Elon has a point here. 👇&lt;/p&gt;

&lt;h2&gt;
  
  
  Building for how users want to consume
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Consider this 1-min clip from the App.js conf intro by Expo’s &lt;a class="mentioned-user" href="https://dev.to/ccheever"&gt;@ccheever&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xu9KbSOKy2M"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  The stats don't lie:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More and more internet use on mobile&lt;/strong&gt; vs. decline on desktop&lt;/li&gt;
&lt;li&gt;90% of that &lt;strong&gt;mobile internet use comes from apps&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Mobile &lt;strong&gt;apps drive up to 3x more conversions&lt;/strong&gt; than browsers (&lt;a href="https://branch.io/journeys/#:~:text=Your%20most%20EFFECTIVE%20conversion%20channel" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Yet we &lt;strong&gt;keep&lt;/strong&gt; (mostly) &lt;strong&gt;focusing on building websites&lt;/strong&gt; instead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you REALLY want to build for how users like to consume, an app in the App + Play Store is what you should aim for.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Consider this section from my current employer's website (&lt;a href="https://www.bothrs.com/service/apps" rel="noopener noreferrer"&gt;Bothrs.com&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.bothrs.com/service/apps" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2Fzwmn80opteg0pjlzoi8q.png" alt="Screenshot from the Bothrs website, explaining why react-native is more cost-efficient for app development" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Native can get you far, but since you want to move fast, building your features twice, once with Swift for iOS and once for Android, is not very efficient in terms of cost &amp;amp; time.&lt;/p&gt;

&lt;p&gt;What you need if you want to build fast, is react-native with Expo.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can still benefit from the web
&lt;/h2&gt;

&lt;p&gt;The fact that your potential users are more likely to prefer mobile, does not mean you can't still massively benefit from the web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Organic web search&lt;/strong&gt; is still a &lt;strong&gt;major source of free traffic&lt;/strong&gt; to your application. When users are searching for answers to their problems, they will more often than not resort to Google first.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All the more reason to show up in their search results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With new Expo features like &lt;a class="mentioned-user" href="https://dev.to/evanbacon"&gt;@evanbacon&lt;/a&gt;'s &lt;a href="https://blog.expo.dev/announcing-the-expo-router-v1-release-candidate-c5680b88a18c" rel="noopener noreferrer"&gt;Expo Router&lt;/a&gt;, you could easily guide users landing on your site from the web, to your mobile app instead:&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;Ok. We're convinced we have to go cross-platform from the start. But how DO we start building for Web/ iOS/ Android with Expo?&lt;/p&gt;

&lt;h3&gt;
  
  
  How to start hybrid-first? 🤔
&lt;/h3&gt;

&lt;p&gt;There are a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🤔 Web-first with e.g. &lt;code&gt;create-react-app&lt;/code&gt;, add &lt;code&gt;react-native&lt;/code&gt; to that setup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🤔 Mobile-first setup, add &lt;code&gt;react-native-web&lt;/code&gt; + Next.js for SSR&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Start from a cross-platform template&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason I don't recommend the first two options is because it &lt;strong&gt;will be hard to figure out&lt;/strong&gt;. Trust me, I know. I've been iterating on this "server render react-native" concept since &lt;code&gt;react-native-web&lt;/code&gt; and Next.js were a thing.&lt;/p&gt;

&lt;p&gt;Let me tell you, it'll take you at least a few days, sometimes weeks before you get to the point you want your initial setup to be.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Also, the clock is ticking, if you're going to beat Twitter, you &lt;strong&gt;can't be reinventing the wheel&lt;/strong&gt; here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Choose an Expo + Next.js template instead.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some honorable mentions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;a href="https://solito.dev/" rel="noopener noreferrer"&gt;Solito.dev&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/nandorojo"&gt;@nandorojo&lt;/a&gt; (check out his talks on Next.js &amp;amp; App.js conf)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;a href="https://github.com/t3-oss/create-t3-turbo" rel="noopener noreferrer"&gt;Create-T3-Turbo&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/t3dotgg"&gt;@t3dotgg&lt;/a&gt; (more opinionated, tRPC for API layer)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These solutions are pretty solid, and you &lt;em&gt;&lt;strong&gt;could&lt;/strong&gt;&lt;/em&gt; start your 'Twitter killer' app from either of these.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...but...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to scale efficiently?
&lt;/h2&gt;

&lt;p&gt;Again: &lt;strong&gt;We're competing with Twitter here.&lt;/strong&gt; You're going to need more engineers. A bunch more.&lt;/p&gt;

&lt;p&gt;How can you ease communication between them, and add some efficiency to a team onboarding new people?&lt;/p&gt;

&lt;p&gt;Essentially, we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔃 Ways to share some reusable building blocks&lt;/li&gt;
&lt;li&gt;📚 Document our ways of working, preferably in 1 space&lt;/li&gt;
&lt;li&gt;🤔 To not spend a lot of time writing these docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here too, we're trying to avoid reinventing the wheel. You don't want new developers to come in, not understand what's going on, and then decide "Meh. I could probably do this better myself."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to go fast, never reinvent the wheel.&lt;/p&gt;

&lt;p&gt;Storybook has a great quote for this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fv1qgly8e4w5toqq3lbqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fv1qgly8e4w5toqq3lbqw.png" alt="Quote: Documentation drives adoption. Storybook logo" width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As good as the other templates are, this is where they tend to fall a bit short. &lt;strong&gt;Docs are essential for any project you intend to ship&lt;/strong&gt;, even if it &lt;em&gt;were&lt;/em&gt; just a small team working on it.&lt;/p&gt;

&lt;p&gt;Here too, there is an honorable mention: Redwood, for example, &lt;em&gt;does&lt;/em&gt; come with Storybook out of the box. Sadly, it is web-first and does not provide a way to do mobile.&lt;/p&gt;

&lt;p&gt;If you want to work on your Twitter alternative efficiently with a lot of devs, &lt;em&gt;you're going to need another template.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing... FullProduct.dev
&lt;/h2&gt;

&lt;p&gt;While searching for inspiration on Twitter, you stumble upon this new template to start from, made by yours truly:&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;Curious about how this automatic generation of storybook docs works, you delve into the &lt;a href="https://fullproduct.dev/docs" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt; to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic docgen with Single sources of truth
&lt;/h2&gt;

&lt;p&gt;What we find is that you can kind of opt into automatic docgen by exporting a &lt;code&gt;getDocumentationProps&lt;/code&gt; variable. This should contain a description of your data structure for your props and works kind of similar to how Next.js has you opt into server-side rendering with e.g. &lt;code&gt;getServerSideProps&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is what the section on automatic docgen in the &lt;a href="https://fullproduct.dev/docs" rel="noopener noreferrer"&gt;FullProduct.dev docs&lt;/a&gt; looks like:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnz7int9yndhxbm2vuylt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnz7int9yndhxbm2vuylt.png" alt="Quickstart docs on how FullProduct.dev achieves automatic documentation" width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You think, &lt;em&gt;ok,&lt;/em&gt; so all I need to do is give the component the same name as the file, export &lt;code&gt;getDocumentationProps&lt;/code&gt;, and some node script/automation will pick it up and spit out some Storybook files? That works for me! &lt;/p&gt;

&lt;p&gt;Some more questions do still come up though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What format should I export my prop structure as?&lt;/li&gt;
&lt;li&gt;Will I have to redefine this structure for my types?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We continue down the quickstart and find the answers to our questions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftbszgeqyvs6vwart0c4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftbszgeqyvs6vwart0c4n.png" alt="Quickstart documentation on how FullProduct.dev allows you to use actual single sources of truth" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This uses &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod&lt;/a&gt; for single sources of truth? Awesome, that's arguably even more type-safe than typescript itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You realize this is a great way to not repeat yourself when it comes to data structure and data validation. Actual "single sources of truth" you can get your type-safety (and editor hints) from, that's pretty neat 🎉 &lt;/p&gt;

&lt;p&gt;While this makes sense to you now, you're curious what these Storybook files actually render docs-wise. So you open up FullProduct.dev's &lt;a href="https://main--62c9a236ee16e6611d719e94.chromatic.com/?path=/docs/features-app-core-icons" rel="noopener noreferrer"&gt;example Storybook&lt;/a&gt; to check that out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fq2cxcp9do6kbpode2e1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fq2cxcp9do6kbpode2e1j.png" alt="Example Storybook page for a component hooking into automatic docgen" width="800" height="771"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You notice that for every component opting into automatic docgen with &lt;code&gt;getDocumentationProps&lt;/code&gt;, there's a markdown powered section in our Storybook with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Import path&lt;/strong&gt; for that component + option to copy&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Preview&lt;/strong&gt; of what the component looks like&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;How to use&lt;/strong&gt; the component in another React component&lt;/li&gt;
&lt;li&gt;✅ Table of all &lt;strong&gt;props, with description, defaults &amp;amp; controls&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can even &lt;strong&gt;edit these controls&lt;/strong&gt; in the table and &lt;strong&gt;see the preview and code example update&lt;/strong&gt; live to test out what each prop ends up doing 👀&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a ton of value you get for describing your prop structure in a specific way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On top of that, the template &lt;strong&gt;supports adding your own Markdown&lt;/strong&gt; for documenting your ways of working. This should help our newly onboarded devs with knowing what's available and how to build with these blocks themselves, instead of going in blind. ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  Web, iOS &amp;amp; Android with Expo + Next.js
&lt;/h2&gt;

&lt;p&gt;With docs tackled, you remember being promised an Expo + Next.js setup as well. So you check out the example page the template came with, either &lt;a href="https://fullproduct.dev/demos?v=dev-to" rel="noopener noreferrer"&gt;online&lt;/a&gt; or by cloning a &lt;a href="https://github.com/FullProduct-dev/green-stack-starter-demo" rel="noopener noreferrer"&gt;demo&lt;/a&gt; &lt;a href="https://github.com/codinsonn/green-stack-demo" rel="noopener noreferrer"&gt;repo&lt;/a&gt; and running &lt;code&gt;npm run dev&lt;/code&gt;:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://fullproduct.dev/demos" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffullproduct.dev%2Fopengraph-image-1e9x92.png%3F191ba5e421ae85d7" height="630" class="m-0" width="1200"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://fullproduct.dev/demos" rel="noopener noreferrer" class="c-link"&gt;
          FullProduct.dev | Universal App Starterkit
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          We help freelancers, agencies, startups and solopreneurs build their apps for Web and App Stores, in an efficient write-once way.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffullproduct.dev%2Ffavicon.ico" width="48" height="48"&gt;
        fullproduct.dev
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.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%2Fec02wwy2bbfz9old8rnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fec02wwy2bbfz9old8rnx.png" alt="Side by side of the example Next.js and Mobile app" width="800" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 To open the iOS simulator as well, use &lt;code&gt;npm run dev:mobile&lt;/code&gt; instead (with XCode installed)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You compare the web &amp;amp; mobile examples side by side, and notice it comes with some pretty basic template stuff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image ➡️ Powered by a &lt;code&gt;src&lt;/code&gt; string pointing to a local file in the public folder&lt;/li&gt;
&lt;li&gt;Icons for each part of the stack ➡️ Click or tap them for more info&lt;/li&gt;
&lt;li&gt;Which file to edit to change this example screen&lt;/li&gt;
&lt;li&gt;Confirmation we have some very basic APIs up and running&lt;/li&gt;
&lt;li&gt;Internal links to test navigation ➡️ URLs on web, stacks on mobile&lt;/li&gt;
&lt;li&gt;External links to e.g. the &lt;a href="https://main--62c9a236ee16e6611d719e94.chromatic.com/?path=/docs/readme-md--page" rel="noopener noreferrer"&gt;Aetherspace docs&lt;/a&gt; ➡️ New tab or in-app browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You decide to take the advice of being told where we can throw out all this boilerplate and start writing our own code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write-once UI
&lt;/h2&gt;

&lt;p&gt;It's time to write some UI for our Twitter alternative.&lt;/p&gt;

&lt;p&gt;In the quickstart, you read up on how to do this in a write-once way:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F316y276oqutgx2cqm51s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F316y276oqutgx2cqm51s.png" alt="FullProduct.dev documentation on how to start writing UI in a write-once way for iOS, Android, and the web" width="678" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With primitives like &lt;code&gt;Text&lt;/code&gt;, &lt;code&gt;View&lt;/code&gt; &amp;amp; &lt;code&gt;Image&lt;/code&gt; you can apparently build &amp;amp; style your UI for all the platforms we want to target. This makes sense, you think, since all apps and components eventually are made up of them.&lt;/p&gt;

&lt;p&gt;You do wonder why the template includes its own primitives though.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Didn't &lt;code&gt;react-native&lt;/code&gt; already have &lt;code&gt;Text&lt;/code&gt;, &lt;code&gt;View&lt;/code&gt; &amp;amp; &lt;code&gt;Image&lt;/code&gt; primitives?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you ponder a bit longer, you realize that if these primitives are optimized for each of these platforms, then anything built with those primitives should also be optimized for them as well.&lt;/p&gt;

&lt;p&gt;To style these primitives for example, you can use tailwind, and some optimizations seem to have been made to make sure things like breakpoints and media queries work for all environments as well. (even though &lt;code&gt;react-native-web&lt;/code&gt; does not support this out of the box, hey, Aetherspace has you covered)&lt;/p&gt;

&lt;h3&gt;
  
  
  Truly native on mobile
&lt;/h3&gt;

&lt;p&gt;Just to be sure, you have a closer look at what your react-native bundle actually looks like.&lt;/p&gt;

&lt;p&gt;What you find is very similar to this tweet:&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;While the aetherspace primitives do seem to be translated by &lt;code&gt;react-native&lt;/code&gt; to (e.g.) native UIKit layers under the hood, and not some sort of webview rendering a website underneath, doesn't mean they're also optimized for web however, and Elon is just waiting to call us out for it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fj5anzxs416iw88aqvn6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fj5anzxs416iw88aqvn6g.png" alt="Potential faked tweet by Elon, taunting us for our Google search rankings" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic HTML &amp;amp; Web optimisations for SEO
&lt;/h2&gt;

&lt;p&gt;We are competing with Twitter as a text-based platform, so ranking high in search engines will be an important factor in our plan to beat Elon at his own game.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you were doing e-commerce instead, this would be even more important.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Luckily, FullProduct.dev provides you with &lt;code&gt;aetherspace/html-elements&lt;/code&gt;, a small wrapper around &lt;a class="mentioned-user" href="https://dev.to/evanbacon"&gt;@evanbacon&lt;/a&gt;'s &lt;a href="https://github.com/expo/expo/tree/main/packages/html-elements" rel="noopener noreferrer"&gt;@expo/html-elements&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftr0hqmyxoqbw2lexidbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftr0hqmyxoqbw2lexidbu.png" alt="FullProduct.dev docs on opting into web optimizations like semantic HTML" width="761" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take what works and make it better&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;☝️ That seems to be a common theme in this aetherspace template thing. Like their &lt;code&gt;Image&lt;/code&gt; primitive using &lt;code&gt;next/image&lt;/code&gt; under the hood, that's bound to improve our SEO ranking:&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;This really solidifies the reason you're choosing a template with Next.js. A close collaboration between their team and the Chrome dev team is likely to help give you a head start when it comes to search engine ranking.&lt;/p&gt;

&lt;p&gt;Ok, but: Server rendering is nice, but what about the rest of your back-end?&lt;/p&gt;

&lt;h2&gt;
  
  
  Write-once APIs
&lt;/h2&gt;

&lt;p&gt;Recognizing you as competition, it doesn't take Elon long before he throws another jab your way:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3qhcmliy2axxz0sbei5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3qhcmliy2axxz0sbei5z.png" alt="Elon asking us whether we even have an API" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, you remember that the example screen the template came with had status badges for not just one, but TWO apis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ REST, powered by &lt;strong&gt;Next.js API routes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ GraphQL, powered by &lt;strong&gt;Apollo Server&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you click either of these buttons, it opens their endpoints.&lt;/p&gt;

&lt;p&gt;For the REST one, you end up at a &lt;a href="https://aetherspace-green-stack-starter.vercel.app/api/health" rel="noopener noreferrer"&gt;heath check endpoint&lt;/a&gt; at &lt;code&gt;/api/health&lt;/code&gt; that tells you the server is alive and kicking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kicking"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you check the &lt;a href="https://fullproduct.dev/api/graphql" rel="noopener noreferrer"&gt;GraphQL playground&lt;/a&gt; at &lt;code&gt;/api/graphql&lt;/code&gt;, you see that the exact same health check is available:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fk6ycjihrrybv260qi6dp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fk6ycjihrrybv260qi6dp.png" alt="Health check query shown in the FullProduct.dev GraphQL Playground" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hmm. Are these two APIs using the same resolver underneath? Could you edit the underlying function once to (e.g.) echo back some argument and bring those API changes to both REST and GraphQL in one go...?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once more, the quickstart provides us with the answer:&lt;/p&gt;

&lt;p&gt;Just to recap what you just learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. Define your data structure for input &amp;amp; output with &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2. You do this using the same &lt;code&gt;aetherSchema()&lt;/code&gt; builder as for docgen&lt;/li&gt;
&lt;li&gt;3. You can reuse properties from one schema in another (&lt;a href="https://fullproduct.dev/docs/single-sources-of-truth" rel="noopener noreferrer"&gt;and more&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;4. Business logic is a regular promise, wrapped with &lt;code&gt;createResolver()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;5. This bundles your logic with your args &amp;amp; response data structure&lt;/li&gt;
&lt;li&gt;6. Export a helper around that bundle to create an API route&lt;/li&gt;
&lt;li&gt;7. Export a &lt;code&gt;graphQLResolver&lt;/code&gt; pointing to that bundle to enable GraphQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you implement this echo functionality and try it out in the GraphQL playground, you notice it now also hints at the docs you defined for your arguments and response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fll1pieh8ywjqoroanek8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fll1pieh8ywjqoroanek8.jpeg" alt="New healthCheck query in apollo playground" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Another win for single sources of truth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  More value in less time
&lt;/h2&gt;

&lt;p&gt;The final part of the quickstart reflects on everything the 'FullProduct.dev' template helps you with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F815h46vhl5q11spddacf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F815h46vhl5q11spddacf.png" alt="FullProduct.dev docs recapping the powerful results of this quickstart" width="643" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fast forwarding our hypothetical scenario a few months: You've managed to grow your team quite a bit. Some ex-twitter engineers have even joined up. Motivated, highly efficient, and sharing knowledge through docs, your team has quickly been able to recreate a lot of core Twitter features and is ready to launch a first MVP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Meanwhile at Twitter HQ...
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fevsx9nj09jqzbbxn8nyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fevsx9nj09jqzbbxn8nyq.png" alt="Elon asking on twitter why it keeps crashing" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You see, Elon made a grave mistake.&lt;/p&gt;

&lt;p&gt;Consider this graphic of what Twitter architecture could look like after 10 years of active development:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/cassiecodes/status/1588531575993794560" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2Fnh3b3p9cwxbmf46n7etp.png" alt="Graphic of twitter architecture leaning on a tiny piece of critical infrastructure that might not have been handed over during twitter layoffs" width="800" height="1009"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thanks to xkcd and &lt;a href="https://twitter.com/cassiecodes/status/1588531575993794560" rel="noopener noreferrer"&gt;cassiecodes&lt;/a&gt; for this graphic&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Perhaps firing half your developers and watching a bunch more quit in solidarity wasn't the smartest thing to do Elon.&lt;/p&gt;

&lt;p&gt;Eventually something important breaks, and we can all expect this to happen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnu9ph7c4gv29ebjhc0gv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnu9ph7c4gv29ebjhc0gv.png" alt="Faked tweet of Elon Musk asking if someone wants to buy Twitter from him" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When that happens, you, your team, and your alternative will be ready to swoop in and provide a fresh new experience for those fleeing Twitter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Things to consider
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fo0xr5q9ljt9htzffw2ug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fo0xr5q9ljt9htzffw2ug.png" alt="Disclaimer" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Obligatory statement to avoid getting banned from Twitter:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsrdumq45u03d1dbhx2eb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsrdumq45u03d1dbhx2eb.png" alt="Faked Elon tweet saying " width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since it's just as important to know when NOT to use a tool:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤔 If you're in need of cutting-edge native features, like building the next Spotify, this probably won't be the template for you. React-native takes a while to have all the latest native features, Expo takes some time to pick up with the latest react-native version, ... but, this &lt;em&gt;could&lt;/em&gt; be fixed with Expo's ability to add &lt;a href="https://docs.expo.dev/workflow/customizing/" rel="noopener noreferrer"&gt;custom native code&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;🤔 Aetherspace is a starter repo. On top of that it's currently still early days. The core features may be there and the quickstart &amp;amp; docs might cover a lot, but there is more testing to be done on larger-scale projects. Attempting to migrate an existing app to the template is probably a bad idea unless you’re rebranding / rebuilding it from scratch.  &lt;/p&gt;

&lt;p&gt;🤔 If you're not a fan of Typescript, Monorepos or even React, this will also not be a good fit. ... but if you're instead familiar with another web-based front-end framework, have a look at Capacitor instead for going cross-platform from the start.  &lt;/p&gt;

&lt;h3&gt;
  
  
  When to use &lt;a href="https://github.com/FullProduct-dev/green-stack-starter-demo" rel="noopener noreferrer"&gt;FullProduct.dev&lt;/a&gt;, from a business perspective:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ You want the best of both worlds. Your users may prefer mobile, but even if they don't, &lt;strong&gt;you're building for both&lt;/strong&gt; in a very efficient way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ You want &lt;strong&gt;free organic web traffic&lt;/strong&gt; as a top source of lead generation. Here, Next.js = first class in SEO ranking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Easily guide users from web to mobile, where more conversions tend to happen.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to use the template, from a DEV perspective:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ You're already familiar with React, and would like to take those skills cross-platform, then this is a great way to do so.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ You want to kickstart your next Expo + Next.js project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ You'd like to automate a large part of your documentation or explore what Storybook can do for you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ You love single sources of truth, DRY programming and providing a ton of value in little time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Links &amp;amp; Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Aetherspace/green-stack-starter-demo" rel="noopener noreferrer"&gt;[Github] FullProduct-dev/green-stack-starter-demo&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/codinsonn/green-stack-demo" rel="noopener noreferrer"&gt;[Github] codinsonn/green-stack-demo&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://fullproduct.dev/docs" rel="noopener noreferrer"&gt;[Quickstart] Starterkit quickstart ⚡️&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://fullproduct.dev/docs/core-concepts" rel="noopener noreferrer"&gt;[Core Concepts] FullProduct.dev docs 📚&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://codinsonn.dev" rel="noopener noreferrer"&gt;[Author] Social links to watch me build this in public&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codegen</category>
      <category>zod</category>
      <category>startup</category>
      <category>trends</category>
    </item>
  </channel>
</rss>
