<?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: Dominik Sumer</title>
    <description>The latest articles on DEV Community by Dominik Sumer (@dominiksumer).</description>
    <link>https://dev.to/dominiksumer</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%2F557134%2F233be635-10e5-4da4-a5e0-726fe5e6ef86.jpg</url>
      <title>DEV Community: Dominik Sumer</title>
      <link>https://dev.to/dominiksumer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dominiksumer"/>
    <language>en</language>
    <item>
      <title>How to use React to generate your own OpenGraph images</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Sat, 31 Jul 2021 20:11:00 +0000</pubDate>
      <link>https://dev.to/dominiksumer/how-to-use-react-to-generate-your-own-opengraph-images-36nj</link>
      <guid>https://dev.to/dominiksumer/how-to-use-react-to-generate-your-own-opengraph-images-36nj</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/how-to-use-react-to-generate-og-images"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In this blog post, I want to show you how you can generate an OpenGraph image out of your React component. Personally, I love this approach, because I can leverage my frontend development skills to generate dynamic images (actually not only OpenGraph images).&lt;/p&gt;

&lt;p&gt;As already stated in the title, I am going to use React to generate the image, but the approach can probably be easily transferred to other Frontend Frameworks too, so I hope you also find it helpful although you're not into React!&lt;/p&gt;

&lt;h1&gt;
  
  
  Using Puppeteer / Playwright
&lt;/h1&gt;

&lt;p&gt;The first building stone for this approach is to use a browser automation framework like Puppeteer or Playwright. Both are very similar feature-wise and also API-wise so there shouldn't be many differences between them. Today I am going to use Playwright.&lt;/p&gt;

&lt;p&gt;Both of the mentioned frameworks can be used to automate a (headless) browser. You can write scripts to navigate to specific websites and scrape them or do other fancy stuff. And for the generation of our OG images, we're leveraging the power to take screenshots of websites. 🙌&lt;/p&gt;

&lt;p&gt;Check out the following snippet:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;playwright&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;playwright-aws-lambda&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;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1200&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;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;630&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launchChromium&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&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;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;height&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenshot&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&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="na"&gt;y&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="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;height&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;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these few lines we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fire up a headless chrome browser&lt;/li&gt;
&lt;li&gt;Open a new tab with the given viewport (I chose 1200x630 because it is the most common og image size)&lt;/li&gt;
&lt;li&gt;Take a screenshot of it - you can choose between PNG or JPEG and with JPEG you can even specify the quality of the image&lt;/li&gt;
&lt;li&gt;Close the browser&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's pretty neat, isn't it? But yeah, we're now just generating a plain white og image - so how can we use React to design a dynamic image of our desire? 😄&lt;/p&gt;

&lt;h1&gt;
  
  
  Leverage the power of React
&lt;/h1&gt;

&lt;p&gt;Imagine we have the following component which we want to use to render our og image:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;title&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OgImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&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;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;60px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&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;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a very simple component, perfect for our example. It takes a title as a prop and renders it as a red text. Let's tell playwright that we want to render it onto our page.&lt;/p&gt;

&lt;p&gt;First we're creating an instance of our React Component passing our desired title as prop:&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;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OgImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a test title&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;And then we're leveraging the power of React server side rendering. We're rendering it as static HTML markup:&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;renderToStaticMarkup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally we add a utility function to render our basic HTML structure:&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;baseCSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`*{box-sizing:border-box}body{margin:0;font-family:system-ui,sans-serif}`&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;getHtmlData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;body&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8"&amp;gt;&amp;lt;style&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseCSS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body style="display:inline-block"&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&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;html&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;And now we tell playwright, right after opening the new page in the browser, that our generated HTML should be set as the content of the page:&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;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getHtmlData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilá now we're rendering our own React component with playwright and taking a screenshot of it. 🥳 From here your imagination knows no boundaries. Just style your og image like you're used to style your frontend applications and use as many dynamic parameters as you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using ChakraUI
&lt;/h2&gt;

&lt;p&gt;I love to use ChakraUI to style my web applications. Since I switched to ChakraUI I would never want to style my React applications differently. Therefore I also wanted to use ChakraUI to generate my og images.&lt;/p&gt;

&lt;p&gt;To achieve this you also need to include the &lt;code&gt;&amp;lt;ChakraProvider&amp;gt;&lt;/code&gt; into your OgImage component so that you can access all of the functionality.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploying it as a serverless function
&lt;/h1&gt;

&lt;p&gt;Basically, you could use this technique to generate images of your React component however you want. E.g. as a Node.js script that generates some images with the given arguments. But with this blog post, I am specifically mentioning og images, which are being fetched when a bot crawls your website.&lt;/p&gt;

&lt;p&gt;I am using &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; to write my React applications and my plan was to actually generate those og images while building my project. Next.js creates static sites for my blog posts and I wanted to create the static og image once the static site is created and then just serve it as static asset. But I didn't get this working on &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; as I ran into memory limits during the build process.&lt;/p&gt;

&lt;p&gt;So then I went for the second-best approach which came into my mind: deploy it as a serverless function (in my case a Next.js API route) which is called with dynamic parameters.&lt;/p&gt;

&lt;p&gt;It's basically just a GET call that takes my dynamic parameters, renders the og image with playwright, and returns it as response. That's how I am rendering the og images for my blog posts here. 😄&lt;/p&gt;

&lt;p&gt;You can find the source of this og image generation &lt;a href="https://github.com/dsumer/portfolio/blob/master/src/pages/api/og-image.ts"&gt;right here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And &lt;a href="https://dominik.sumer.dev/api/og-image?title=How%20to%20integrate%20Gumroad%20as%20a%20payment%20provider%20for%20your%20SaaS&amp;amp;slug=/blog/how-to-integrate-gumroad-as-payment-provider-for-your-saas&amp;amp;date=June%2030%2C%202021&amp;amp;rt=8%20min%20read"&gt;this is the API&lt;/a&gt; where those og images are located / being generated on the fly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope this blog post was somehow helpful to and maybe it could spark some ideas about how you can use this technique to generate some awesome images. If you have further questions, please don't hesitate to &lt;a href="https://twitter.com/messages/compose?recipient_id=798465058061881344"&gt;shoot me a DM on Twitter&lt;/a&gt;, cheers!&lt;/p&gt;

</description>
      <category>react</category>
      <category>playwright</category>
      <category>opengraph</category>
      <category>image</category>
    </item>
    <item>
      <title>How to integrate Gumroad as a payment provider for your SaaS</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Wed, 30 Jun 2021 13:20:45 +0000</pubDate>
      <link>https://dev.to/dominiksumer/how-to-integrate-gumroad-as-a-payment-provider-for-your-saas-17k</link>
      <guid>https://dev.to/dominiksumer/how-to-integrate-gumroad-as-a-payment-provider-for-your-saas-17k</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/how-to-integrate-gumroad-as-payment-provider-for-your-saas"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Maybe you’ve read my article about &lt;a href="https://dominik.sumer.dev/blog/stripe-checkout-eu-vat"&gt;Stripe and EU VAT&lt;/a&gt; (which thankfully is deprecated because of the release of &lt;a href="https://stripe.com/at/tax"&gt;Stripe Tax&lt;/a&gt;) if you wanted to integrate a payment provider for your SaaS. And maybe you also thought: why does it have to be so complicated?&lt;/p&gt;

&lt;p&gt;Most of the payment providers managed to make the process of selling digital products and taxes much easier for us over the last months. But do you know what’s even better than making the process easier? If you don’t have to deal with it at all 🙌&lt;/p&gt;

&lt;p&gt;Today I want to introduce you to another way to integrate payments into your SaaS. A way that is super simple and causes you no headaches - which is crucial if you’re currently building an MVP for your SaaS and want to validate if your idea is worth pushing further.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Gumroad and how does it work?
&lt;/h1&gt;

&lt;p&gt;Gumroad is a digital marketplace for selling your products. It’s not even bound to only sell digital products, but today we’re going to focus on this part. Gumroad handles many things for you. You only need to connect your PayPal account (or in some countries you can also connect your bank account directly) to your Gumroad account and that’s it.&lt;/p&gt;

&lt;p&gt;But why is it so easy when it comes to handling invoices and the collection of taxes? Because you are not selling the digital products to your customers directly, but on the behalf of Gumroad and they take care of all the bureaucracies.&lt;/p&gt;

&lt;p&gt;That means that Gumroad is responsible for charging the VAT from your customers and invoice them correctly. And every two weeks Gumroad pays you all the income of your products onto your PayPal account.&lt;/p&gt;

&lt;p&gt;If you now still need to invoice Gumroad, because it is needed for your company, then you probably just need a reverse-charge invoice to have everything documented, but make sure to double check this with your accountant.&lt;/p&gt;

&lt;p&gt;This of course doesn’t come for free. Gumroad charges you a fee for every sale, but especially when you’re at the beginning and don’t want to spend too much time on bureaucracies, it can definitely be worth it. &lt;a href="https://help.gumroad.com/article/66-gumroads-fees#TheAllInclusiveGumro"&gt;This table&lt;/a&gt; includes all different use-cases and how much fees you’d have to pay in each case.&lt;/p&gt;

&lt;h1&gt;
  
  
  How can Gumroad be included in your payment process?
&lt;/h1&gt;

&lt;p&gt;There are two ways how you can include Gumroad in your payment process. Let’s split them up into two sections and look at each of them on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirect your customer to Gumroad
&lt;/h2&gt;

&lt;p&gt;The first approach is to just redirect the user to your product page in Gumroad where they’re able to purchase it directly. This is the approach we went for because it is straightforward to implement and we had problems with the other solution (more on this later).&lt;/p&gt;

&lt;p&gt;So you lead the user to the URL of your Gumroad product which looks like this: &lt;a href="https://gum.co/snappify"&gt;https://gum.co/snappify&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In most cases you describe your product very well on the website of your SaaS, so you probably don’t need to show him the description page on Gumroad, but directly lead him to the checkout page. This can be achieved by appending &lt;code&gt;?wanted=true&lt;/code&gt; as request parameter of the URL.&lt;/p&gt;

&lt;p&gt;So for example if you have your own pricing form, where you describe everything to the user and then lead him to the Gumroad URL of the product including the &lt;code&gt;wanted&lt;/code&gt; parameter, he will directly land at the checkout and can purchase your product - Gumroad is taking care of the rest. 🙌&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom form fields and how to prefill them
&lt;/h3&gt;

&lt;p&gt;Sometimes you also have other form fields than just the email address of the user. In our case we need the Twitter Username so we can identify which user has purchased our product.&lt;/p&gt;

&lt;p&gt;Those form fields can be specified directly in Gumroad. You can also specify if they are mandatory or not.&lt;/p&gt;

&lt;p&gt;Sometimes you already have that information from the user and want to save him time by prefilling it in the Gumroad checkout form. This can also be achieved by just appending the values via request parameters.  &lt;/p&gt;

&lt;p&gt;In our case, we named the field “Twitter Username”. So to prefill it with a value we need to append the following parameter: &lt;code&gt;&amp;amp;Twitter%20Username=dominiksumer&lt;/code&gt;  &lt;/p&gt;

&lt;p&gt;That’s it, you probably saved your user time which is definitely great in a checkout process. 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  Get notified about successful purchases
&lt;/h3&gt;

&lt;p&gt;After your user successfully purchased your product, he will be redirected to an URL you can specify in Gumroad. We for example made a separate page where we thank the user and tell him how he can further continue.  &lt;/p&gt;

&lt;p&gt;But like this, you still don’t know which user purchased and you would also probably need to mark this user as a customer in your database to give him access to features he paid for. This is where &lt;a href="https://gumroad.com/ping"&gt;“Gumroad Ping”&lt;/a&gt; comes into play.&lt;/p&gt;

&lt;p&gt;Gumroad Ping is a service that calls your application if a successful purchase was made. Perfect for our use case! So how does such a call look like and how can you react to it?  &lt;/p&gt;

&lt;p&gt;Gumroad Ping sends a POST request to your specified URL with all information you need about the purchase. Here is an example of how we implemented our REST endpoint to handle the Gumroad Ping call:&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;addPro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPro&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;../../utils/auth-util&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&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;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toUpperCase&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;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PRODUCT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Twitter Username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;isPro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;addPro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userName&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you can see we first check that the boolean &lt;code&gt;test&lt;/code&gt; is false (would be true if you’re testing a purchase on your own) and if the productId is matching the id of your product. Like this, you also secure the endpoint against someone just calling it and therefore you should keep the product id a secret.&lt;/p&gt;

&lt;p&gt;Afterward, you can read other information from the user and move on with your logic of persisting the user as a customer. Also, make sure to return the status code 200 if everything worked out well to tell Gumroad that the operation was performed successfully. If you return another status code, Gumroad will assume that there was a failure and going to retry the Gumroad Ping call up to three times.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hint: When Gumroad is redirecting the user to your website after a purchase, it can be the case that Gumroad Ping didn’t notify your application about it yet. So make sure to deal with this asynchrony.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Embed Gumroad Widgets into your SaaS
&lt;/h2&gt;

&lt;p&gt;The second approach is to embed one of the &lt;a href="https://gumroad.com/widgets"&gt;Gumroad widgets&lt;/a&gt; into your website. It aims for an even better user experience as the user doesn’t have to leave your website for purchasing your product.&lt;/p&gt;

&lt;p&gt;I had some weird issues in the frontend and didn’t want to waste too much time with problem-solving. That’s why I chose the first method with redirecting the user to Gumroad directly. I would suggest you give it a try and see if it works and fits your needs!&lt;/p&gt;

&lt;h1&gt;
  
  
  Recurring payments
&lt;/h1&gt;

&lt;p&gt;For our product &lt;a href="https://snappify.io"&gt;snappify&lt;/a&gt; we wanted to keep things simple in the beginning and only offer a yearly subscription without recurring payments. This yearly subscription is just a normal Gumroad product and when we get notified via Gumroad Ping that someone purchased the product, we add him as “Pro user” to our database including the date of the purchase and that’s it.&lt;/p&gt;

&lt;p&gt;But you maybe want to including a monthly subscription too and want to charge your customers on a monthly basis. Gumroad also got you covered on this! They call it &lt;a href="https://gumroad.com/gumroad/p/introducing-gumroad-memberships"&gt;Memberships&lt;/a&gt; and together with the &lt;a href="https://gumroad.com/api"&gt;Gumroad API&lt;/a&gt; you have powerful tools at your hand to offer monthly subscriptions to your customers with ease.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;You’re currently bootstrapping your SaaS. You want to find out if your idea is worth pushing further. You want to concentrate on getting the MVP up and running instead of spending hours integrating a payment provider.&lt;/p&gt;

&lt;p&gt;If you find yourself in those words, you should consider giving Gumroad a try. They take their fair share, but also take care of all the bureaucracies and the integration itself should be done in some hours.&lt;/p&gt;

&lt;p&gt;I’ve put this blog post out there because I have the feeling that only providers like &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; or &lt;a href="https://paddle.com/"&gt;Paddle&lt;/a&gt; are being mentioned when integrating a payment provider for your SaaS. But why not keep it even simpler in the beginning? Cheers!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>saas</category>
      <category>vat</category>
      <category>gumroad</category>
    </item>
    <item>
      <title>Publishing a TypeScript library in 2021</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Mon, 31 May 2021 22:16:59 +0000</pubDate>
      <link>https://dev.to/dominiksumer/publishing-a-typescript-library-in-2021-2hpd</link>
      <guid>https://dev.to/dominiksumer/publishing-a-typescript-library-in-2021-2hpd</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/publishing-a-typescript-library-in-2021"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The last time I wanted to publish a npm package was in 2018. It was a React library which provided Components for creating Forms. It wasn’t straight forward to publish such a library back then. There were so many things to handle on your own, especially when it came to TypeScript.&lt;/p&gt;

&lt;p&gt;Assuming you were already familiar with webpack you thought: ha that should be easy, I can leverage my knowledge and just bundle my library with webpack, exactly like my webapps. But then you suddenly heard of that new kid on the block: Rollup, which seems to do a better job at bundling libraries. So a new bundler you had to learn and still soo many things to take care of on your own.&lt;/p&gt;

&lt;p&gt;Great that we’re now fast forwarded to 2021. There’s a new player in the town to make our lives a &lt;strong&gt;lot&lt;/strong&gt; easier. Let me introduce you to &lt;a href="https://tsdx.io"&gt;tsdx.io&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The zero-config CLI for TypeScript package development
&lt;/h1&gt;

&lt;p&gt;The goal of TSDX is to get rid of all the pain points I’ve mentioned above and just let you focus on developing your next library, instead of “waste another afternoon on the configuration”. And oh boy, they did an excellent job with this. Actually I couldn’t find the meaning of the TSDX acronym, but I would assume that it stands for TypeScript Developer Experience - which it definitely enhances a lot.&lt;/p&gt;

&lt;p&gt;TSDX comes with Rollup under the hood and serves you best-practices for developing and bundling modern NPM packages. It is well tested and the team behind TSDX is &lt;a href="https://formium.io/"&gt;formium&lt;/a&gt;, who are also developing the popular React forms library &lt;a href="https://github.com/formium/formik"&gt;formik&lt;/a&gt;, which also leverages the power of TSDX.&lt;/p&gt;

&lt;p&gt;The best practices which come with TSDX are should be perfect for the most projects. And even if you need to do adjustments, the process is pretty straight forward. For example for &lt;a href="https://github.com/snappify-io/integration"&gt;our library&lt;/a&gt; we wanted to use less instead of writing plain css and I show you later how you can adjust the TSDX config to your needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create a new TSDX project
&lt;/h1&gt;

&lt;p&gt;Creating a new TypeScript library with TSDX is as easy as typing &lt;code&gt;npx tsdx create mylib&lt;/code&gt;. The command will guide you through the setup process and afterwards you will be presented with a packed folder where you can just start to develop your new library.&lt;/p&gt;

&lt;p&gt;TSDX has three different project types which you have to choose one from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;basic&lt;/code&gt; - a plain TypeScript project (we went for this because we didn’t want to include any external libraries and weren’t depending on React)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react&lt;/code&gt; - choose this if you want to create a React library. TSDX takes care of all the things that have to be done to create a React library out of the box&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-with-storybook&lt;/code&gt; - same as the option before, but including a Storybook setup&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;TSDX makes developing your library super easy. You can just type &lt;code&gt;npm start&lt;/code&gt; and your project will be started in watch mode. The entry point for your library is the &lt;code&gt;index.ts&lt;/code&gt; (or &lt;code&gt;index.tsx&lt;/code&gt; for a React project). So when ever you will make changes in one of your corresponding files, TSDX will detect it and rebuild the library for you, leading to a super smooth development process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running tests
&lt;/h2&gt;

&lt;p&gt;TSDX also comes with Jest setup for you, so you could just write tests for example in the format of &lt;code&gt;*.test.ts(x)&lt;/code&gt; files and Jest will recognise and execute them automatically if you type &lt;code&gt;npm test&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try out your library locally
&lt;/h2&gt;

&lt;p&gt;Of course you also want to try out your library locally before you publish it for the rest of the internet. For this, the npm command &lt;code&gt;npm link&lt;/code&gt; comes in very handy.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go into the folder of your library &lt;code&gt;cd ~/projects/mylib&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;npm link&lt;/code&gt; to create a local symlink of your library&lt;/li&gt;
&lt;li&gt;Switch to the folder of your project where you want to include your library &lt;code&gt;cd ~/projects/myproject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;npm link myproject&lt;/code&gt; to install the local version of your library. This will also automatically refresh if you make changes to your library and rebuild it (which is done automatically by the watch command if you execute &lt;code&gt;npm start&lt;/code&gt; in your library project)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Be sure to execute npm link with your full library name (not the folder name) which is stated in your package.json. In our case we had to type &lt;code&gt;npm link @snappify/integration&lt;/code&gt; because our package is included in our npm organisation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adjust the default configuration
&lt;/h1&gt;

&lt;p&gt;Now you should be ready to develop your library and test it until you can publish it. But what if the default TSDX doesn’t fulfil your needs and you need to adjust the config?&lt;/p&gt;

&lt;p&gt;If you want to adjust the build config, you have to create a &lt;code&gt;tsdx.config.js&lt;/code&gt; file in the root of your library folder. In their you’re going to export an object including a &lt;code&gt;rollup&lt;/code&gt; function which receives the default rollup config as the first argument and expects the modified config as the return value.&lt;/p&gt;

&lt;p&gt;Here you can see our config where we added the possibility to write less instead of css:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;less&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-less&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rollup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;less&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;insert&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;output&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="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&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;h1&gt;
  
  
  Add helpful GitHub workflows
&lt;/h1&gt;

&lt;p&gt;For a serious library, a good set of GitHub workflows are very helpful! For example you want to execute the tests or linter on every commit or you want to have a size-limit bot commenting on your pull requests if a change leads to a big increase of your package size.&lt;/p&gt;

&lt;p&gt;Fell free to take a look at &lt;a href="https://github.com/snappify-io/integration/tree/main/.github/workflows"&gt;our GitHub workflows&lt;/a&gt; where we setup all of this for our open source library. The configuration for the size-limit can be found in &lt;a href="https://github.com/snappify-io/integration/blob/main/package.json#L37"&gt;our package.json&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Publish your library
&lt;/h1&gt;

&lt;p&gt;So you know have everything adjusted to your needs, had enough time to develop your library and try it out locally. You’re convinced: your library is ready to be consumed by the public! So what’s next?&lt;/p&gt;

&lt;p&gt;During the installation process TSDX made sure to add a proper &lt;code&gt;prepare&lt;/code&gt; script in your &lt;code&gt;package.json&lt;/code&gt;. The prepare script is executed automatically every time you execute &lt;code&gt;npm publish&lt;/code&gt; - so every time you want to publish a new version of your package.&lt;/p&gt;

&lt;p&gt;And what TSDX does before publishing is creating a new TSDX build, which also makes sure the build is not failing before you publish your new version. So you basically only have to run &lt;code&gt;npm publish&lt;/code&gt; (with the corresponding arguments you want to put, e.g. if it should be published publicly) and you’re good to go!&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;When we wanted to create a npm package for our product &lt;a href="https://snappify.io/"&gt;snappify&lt;/a&gt; I was a bit scared because I remembered all the pain points of creating such a library some years ago. I knew of &lt;a href="https://react-hot-toast.com/"&gt;react-hot-toast&lt;/a&gt; by &lt;a href="https://timo.sh/"&gt;Timo Lins&lt;/a&gt; and asked him if he had some tips to get started with creating a TypeScript npm package.&lt;/p&gt;

&lt;p&gt;He pointed me to TSDX and I started to play around with it and got hooked pretty quickly. The default best practices are just working and as the website states: you can focus on developing your library instead of trying to figuring out the right configurations.&lt;/p&gt;

&lt;p&gt;I want to thank the whole team behind TSDX for creating this awesome tool and take a great pain away from creating TypeScript libraries. 🎉&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>library</category>
      <category>npm</category>
      <category>tsdx</category>
    </item>
    <item>
      <title>How my first side project made me the developer I am today</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Wed, 21 Apr 2021 13:24:20 +0000</pubDate>
      <link>https://dev.to/dominiksumer/how-my-first-side-project-made-me-the-developer-i-am-today-23gm</link>
      <guid>https://dev.to/dominiksumer/how-my-first-side-project-made-me-the-developer-i-am-today-23gm</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/how-my-first-side-project-made-me-the-developer-i-am-today"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;It was in 2010 (at the time of writing this it’s already 11 years ago 😱) when I started my first side project in software development. I was 16 years old, still in technical high school, already a bit into programming and hungry for more.&lt;/p&gt;

&lt;p&gt;Some days ago &lt;a href="https://twitter.com/scholz_felix/status/1380889066292588544"&gt;Felix started a thread about people’s first projects&lt;/a&gt; where I also answered with mine. Memories flashed back into my mind and whilst thinking more about it, I thought it could be interesting to share how this project made me the developer I am today. Let me take you with me on this journey. 🗺&lt;/p&gt;

&lt;h1&gt;
  
  
  I wanted to make a game, but it should be online!
&lt;/h1&gt;

&lt;p&gt;Since I was a little boy I had a faible for gaming. It started with the Gameboy Color and the good old Pokemon series and continued with playing multiplayer games like World of Warcraft (which actually led me to repeat one year in school, but that’s another story 😅). So if you mix a strong interest for gaming with a dedication to learn more about programming, it was pretty clear that I wanted to build a game, a multiplayer game to be more precise.&lt;/p&gt;

&lt;p&gt;In 2009 I played around with the &lt;a href="https://www.rpgmakerweb.com/products/rpg-maker-xp"&gt;RPG Maker XP&lt;/a&gt;. I enjoyed to create my little RPGs by clicking around in the editor and always looked up to the cool other creators who created custom scripts with Ruby. I also wanted to create more sophisticated solutions with those scripts, but couldn’t wind my head around it.&lt;/p&gt;

&lt;p&gt;But as I learned more in school I also got a better understanding of those scripts. And I began to google how I can extend my single player RPG Maker game to a multiplayer game with a server. After quite some searching I found that the RPG Maker isn’t the right platform for multiplayer games. It was somehow possible, but people suggested to use other solutions which are built on top of „real“ client-server structures. That’s when I stumbled upon &lt;a href="https://forum.eclipseorigins.com/"&gt;Eclipse&lt;/a&gt;, not the IDE, but a 2D multiplayer game engine written in VB6 (which was already quite old back then lol).&lt;/p&gt;

&lt;h1&gt;
  
  
  I learned the most by playing around with existing solutions
&lt;/h1&gt;

&lt;p&gt;Yes you read correctly, VB6. If you research &lt;a href="https://de.wikipedia.org/wiki/Visual_Basic_Classic"&gt;Visual Basic 6&lt;/a&gt; you can see that it was first released in 1998. So it was already quite old back then, in fact so old that Microsoft killed the support for it some years later with Windows 8. So why did I even start with this? Why did I spend time on that if there could’ve been more state of the art things out there? Because it was completely open source and ready to be studied and modified to my needs.&lt;/p&gt;

&lt;p&gt;It maybe sounds a bit stubborn that I just choose this as my first solution, but I fell in love with the possibilities it offered and even more with the fact that I could tweak it to my needs. It was such an incredible feeling when I changed something in the existing codebase and saw that my changes did what I wanted them to do. This is when the real passion for software development started to sparkle inside of me. As I am writing this I can feel again how it was back then and it also makes me aware of why I am still having such a big passion: typing in some code and seeing it actually does what I want - magic.&lt;/p&gt;

&lt;p&gt;Whilst exploring this game engine I had so many Aha! moments. It was the first time I used something like a client-server architecture and the first time hearing the word “Socket”. I learned so many things by exploring the source code, engaging with the communities and googling different solutions for the tweaks I would like to do. I started to understand that Google is the tool I need in order to learn new things and find solutions for my problems. And I learned that you could solve almost every problem if you just search thoroughly and, more important, search with the right keywords.&lt;/p&gt;

&lt;h1&gt;
  
  
  Finding out what sort of game I wanted to build
&lt;/h1&gt;

&lt;p&gt;I definitely spend 6-12 months with this VB6 engine. After some time I had an idea what I wanted to build: a farming game. I loved &lt;a href="https://de.wikipedia.org/wiki/Harvest_Moon_(Spieleserie,_2007)"&gt;Harvest Moon&lt;/a&gt; and I wasn’t aware of an online game with the same gaming principles as Harvest Moon. The more I thought about it, the more I liked the idea of building a farming game where players can interact with each other. &lt;/p&gt;

&lt;p&gt;So I explained my idea in different forums and got quite some feedback! The most engaging community was a forum called “Harvest Moon Heaven” where the people loved the idea of having a Harvest Moon like game with online functionalities. I got so many positive feedback which increased the motivation to move on with the game even more. It was also the place where I made a poll with how I should name the game and that’s when &lt;a href="http://www.risingfarms-online.com/"&gt;Rising Farms Online&lt;/a&gt; was born. ☺️&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up my own forum / website
&lt;/h1&gt;

&lt;p&gt;The community around Rising Farms grew further. We had an own sub-forum at Harvest Moon Heaven with already quite some people engaging and it was time to create an own place for the game. I bought the &lt;code&gt;risingfarms-online.com&lt;/code&gt; domain and installed a &lt;a href="https://www.woltlab.com/de/"&gt;Woltlab Burning Board&lt;/a&gt; there. The Rising Farms community was born. 🥳&lt;/p&gt;

&lt;p&gt;I also created a landing page. Back then I worked much with Photoshop. I designed the whole Website in PS and sliced the pieces into images which I then imported in my website. Good. old. times. Although the asset size and the source code of the website was terrible 😅.&lt;/p&gt;

&lt;p&gt;Sadly &lt;a href="https://web.archive.org/web/20130618043718/http://www.risingfarms-online.com/"&gt;the earliest version of the website&lt;/a&gt; I could find on Wayback Machine is from 2013, but it gives you a nice impression 😄&lt;/p&gt;

&lt;h1&gt;
  
  
  In 2011 we’ve built a small team around Rising Farms
&lt;/h1&gt;

&lt;p&gt;The community grew further and in 2011 we even built a small team of about 6 people who handled different tasks. All of those people were volunteers who thought that Rising Farms would be a great game and offered their time and skills for free. Some of them were story tellers and built the background story of the game and its characters. Others were graphic artists who created ingame pixel art. &lt;/p&gt;

&lt;p&gt;It was such a great feeling to work with other people on my own project, we even had weekly meetings where we discussed upcoming tasks. I learned many things at this time and even a bit of project management and team coordination.&lt;/p&gt;

&lt;h1&gt;
  
  
  Next Stop: C# with XNA
&lt;/h1&gt;

&lt;p&gt;As the game developed further it got clear to me, that VB6 isn’t a sustainable base for Rising Farms. At the end of 2010 I already had a prototype available and some people tested it, but every time it was a huge pain to get the game running on different machines, due to different dependencies which had to be installed. And when Microsoft announced that they are going to drop the support with their next Windows version it was clear to me that I have to find another way to develop Rising Farms.&lt;/p&gt;

&lt;p&gt;In school we played a bit with XNA, a game framework for C#, and so I thought it could be a nice framework to develop Rising Farms. I spent &lt;strong&gt;much&lt;/strong&gt; time with rewriting existing logic in XNA with C#. Also the Server was rewritten in C# and at the end of 2011 I again had a new version up and running. You can get an overview about how the game looked like in that time with this &lt;a href="https://www.youtube.com/watch?v=aB1j0G4NdMg"&gt;Trailer on Youtube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the beginning of 2012 I even played around with a Web based UI inside the map editor of Rising Farms which you can see in &lt;a href="https://www.youtube.com/watch?v=F5dNJQGDYAI"&gt;this video&lt;/a&gt;. Maybe you can remember those jQuery UI Accordion elements. 😅&lt;/p&gt;

&lt;p&gt;And then followed a long break. I was busy with finishing school and having fun with my friends.&lt;/p&gt;

&lt;h1&gt;
  
  
  Could Rising Farms run in the Browser?
&lt;/h1&gt;

&lt;p&gt;The long break lasted until the mid of 2013. Although I made that break, I never stopped thinking about Rising Farms. I still wanted to push it further and when I finished school in June 2013, I had enough time to pick it up again. &lt;/p&gt;

&lt;p&gt;In school I learned about HTML5. It was the new kid on the block and there were even possibilities like drawing on a canvas or even using WebGL for rendering  graphics with quite a good performance. I didn’t want Rising Farms to be a typical browser game where you can just play around with data via the user interface. It should be a game where players can interact with the world and other players around them. But I thought: if that’s possible in a browser, why not?&lt;/p&gt;

&lt;p&gt;Additionally I still had a problem with the C# client back then: bringing updates to the users. Of course there were also solutions for this out there, but I tried to write my own updater application as you know it from WoW or League of Legends. It also kinda worked but I was never 100% happy with the solution. If the game would be running in the browser, I just needed to serve the latest assets and voilà: the user is running on the latest version, no outdated clients anymore.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let’s bet on JavaScript
&lt;/h1&gt;

&lt;p&gt;So I got hooked on that idea of running the game in the browser. Therefore I needed to learn more about JavaScript, what the possibilities are and what I could do with it. Soo many new things to learn: WebGL, WebSockets, playing sounds, capturing keyboard and mouse events, studying the DOM. A whole new world opened up: the world of web development, and I loved it as much as I still do today.&lt;/p&gt;

&lt;p&gt;I had so much fun doing all of this I can’t even express it into words right now. Back then I used &lt;a href="https://en.wikipedia.org/wiki/Google_Closure_Tools"&gt;Google Closure&lt;/a&gt; for building the client. Frameworks like React or Angular didn’t exist yet, but Closure already offered a big set of opportunities. It came with dependency injection and the possibility to write UI components, I was quite happy with it especially as I didn’t knew of anything else 😅.&lt;/p&gt;

&lt;p&gt;For rendering the game itself I used &lt;a href="https://www.pixijs.com/"&gt;pixi.js&lt;/a&gt;, a 2D rendering engine which used WebGL if the browser supported it, or even had a fallback for “normal” canvas rendering. It is a great framework and made my life so much easier, I would even recommend it today as it is still maintained!&lt;/p&gt;

&lt;h1&gt;
  
  
  Let’s completely bet on JavaScript
&lt;/h1&gt;

&lt;p&gt;Ok, so it was clear to me: I wanted to write the client in JavaScript and it should be executable in the browser. I didn’t want to write a typical browser game but the changes which came with HTML5 gave me the possibilities to create a nice game which could be experienced in modern browsers without any drawbacks.&lt;/p&gt;

&lt;p&gt;Now the question came up: what about the server? The VB6 and C# solutions all had the benefit that I only had to write my DTOs once and then they could be used both on the client and on the server. Initially my plan was to reuse my existing C# server where the WebSocket on the JavaScript client could connect to. Then I would either need to find a way to create JavaScript DTOs out of my C# DTOs, or just write them twice.&lt;/p&gt;

&lt;p&gt;But then I heard about Node.js and that JavaScript can be executed on the server 😱. I thought: that could be exactly what I need. I digged more into it and also fell in love pretty quickly. Again I could reuse the same DTOs on the client and the server and also reuse some of the logic.&lt;/p&gt;

&lt;h1&gt;
  
  
  I had so much fun
&lt;/h1&gt;

&lt;p&gt;Now followed a great time. From June 2013 to February 2014 I spent very much time with Rising Farms. School was finished and at military service (which started in September 2013) I had the luck to also have much free time I could use for developing my dream game. It wasn’t only the client and the server of the game, I even built a separate editor application where all of the game data could be managed including a fully fletched map editor.&lt;/p&gt;

&lt;p&gt;Sorry, I don’t want to brag here. I am just re-experiencing all of this again while writing this blog post and want to emphasize what a strong passion can sparkle inside of you when you’re working on something you truly thrive for.&lt;/p&gt;

&lt;p&gt;Additionally the community also supported me genuinely. If there are people which cheer you up and wait for the next update, it’s definitely a huge motivation boost. I remember release days where 20 people moved around in-game and played together with each other. Great times!&lt;/p&gt;

&lt;h1&gt;
  
  
  It ended in 2014 when I started with my first full-time job
&lt;/h1&gt;

&lt;p&gt;You see, I could write endlessly about my first side project. It was the project of my dreams and I still think of it here and then. But there came a point where I had to stop developing on it. It was the time when I started to work full-time as a software developer.&lt;/p&gt;

&lt;p&gt;Additionally me and my wife (back then we weren’t married yet) moved into our first flat and many new responsibilities popped up why I had to stop the development of Rising Farms. Actually the real “stop” was 1-2 years afterwards, because before I could never really finish with it completely. I did refactorings, e.g. from JavaScript to CoffeScript - never finished. Then from JavaScript to TypeScript, moving away from Google Closure and using React as UserInterface - never finished. And at a specific time I just had to put it to an end. But to be honest I didn’t completely finish with it until today, as the domains and server, including the website, forum and the game itself are still running. And I think I still can’t shut it off completely because maybe … someday … I have time again - I don’t know. 😅&lt;/p&gt;

&lt;p&gt;But even if I never ever find time for it, or instead move on with another, similar project, it’s cool that the website is still online and I can refer to it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Ooh this was a nice journey through the past, thanks for reading it until the end as this story means very much to me. While writing this blog post I again got aware of how many things I learned in that time and how it really made me the developer I am today. I am so grateful for that time and that I found my passion with this project. In the end, it led me to web development which I still love doing today.&lt;/p&gt;

&lt;p&gt;So I can’t stress this enough: if you have a passion for something, just get started with it and you will get better and better. Everyone is different. Some learn better with audio, some with visuals. For me I just learn the most by doing. By experimenting with existing things, look at them from different angles and try to understand how they work. And if you additionally can create something you’re also connected with, beautiful things emerge. &lt;/p&gt;

&lt;p&gt;Currently I am experiencing such a thrive again. I am pursuing my dream of creating a product which brings great value to its users and is financially profitable so that me and my family can live from it. I do this together with my friend Anki and we are active in the makers community of Twitter, which is so helpful and inspiring - feel free to join us there! 🙌&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>sideprojects</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The tech stack of our first SaaS and what we regret</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Wed, 31 Mar 2021 20:01:41 +0000</pubDate>
      <link>https://dev.to/dominiksumer/the-tech-stack-of-our-first-saas-and-what-we-regret-52o8</link>
      <guid>https://dev.to/dominiksumer/the-tech-stack-of-our-first-saas-and-what-we-regret-52o8</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/tech-stack-of-trueq"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;It was in March 2020 when &lt;a href="https://twitter.com/AnkiCodes"&gt;Anki&lt;/a&gt; and I decided to tackle a new project together. After years of abandoning projects in our free time, we were dedicated to spend some time and get this done. I won’t dive deep into this journey, as we‘ve already described it in &lt;a href="https://trueq.io/our-journey"&gt;this blog post&lt;/a&gt; if you’re interested. 😄&lt;/p&gt;

&lt;p&gt;But we did it: we created a new SaaS which is called &lt;a href="https://trueq.io/"&gt;TrueQ&lt;/a&gt; and also managed to deploy it productively. In this blog post I want to talk about the tech stack we used, decisions we made and what we regret or would do differently nowadays.&lt;/p&gt;

&lt;h1&gt;
  
  
  Our Background
&lt;/h1&gt;

&lt;p&gt;Let me tell you a bit about our background in software development. We’re both professional software developers being specialized in web development. In general we’re doing full stack development, although we may have more experience in the frontend. But as we strive to work more on our own products in the future, we definitely have a strong passion to build a product in whole.&lt;/p&gt;

&lt;p&gt;Before choosing the tech stack in detail, it was clear to us that it will be located in the JavaScript ecosystem, or to be more specific, the TypeScript ecosystem. In our jobs we also worked on different backend applications which were written in Java / Kotlin or C#. But our main experience lies in Node.js. Additionally we are building React Applications since almost 6 years, so this is definitely the frontend framework of our choice (and also will be for future projects).&lt;/p&gt;

&lt;h1&gt;
  
  
  Requirements for our product
&lt;/h1&gt;

&lt;p&gt;We knew that for our product SEO is essential. TrueQ is all about finding solutions to your day-to-day problems. So these solutions have to be found easily. Therefore we knew that even though we want to build a rich web application, our server still needs to serve the content of our pages in plain HTML to make search engine’s life easier - server side rendering was a requirement for us.&lt;/p&gt;

&lt;p&gt;We read about &lt;a href="https://mxstbr.com/thoughts/tech-choice-regrets-at-spectrum/"&gt;Max Stoibers regrets when he built spectrum&lt;/a&gt;, and we were sure that we don’t want to implement SSR on our own. 😅 As we were following Vercel and the development of Next.js, it was the first thing we took a closer look at.&lt;/p&gt;

&lt;p&gt;Additionally there was &lt;a href="https://dev.to/olup/the-tools-for-the-job-how-i-code-frontend-apps-in-2020-e03"&gt;an excellent blog post&lt;/a&gt; released at the time we started our project. &lt;a href="https://dev.to/olup"&gt;Loup Topalian&lt;/a&gt; wrote about frameworks and libraries he would use to build a webapp in 2020 and in the end we actually adopted most of them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Next.js
&lt;/h1&gt;

&lt;p&gt;Ok lets begin with the foundation of TrueQ: &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;. It is a production-ready React framework developed by &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; and they’re not stopping at the client-side. With Next.js it is possible to write a fully fletched universal webapp which also takes care of server side rendering and other amazing stuff like incremental static site generation.&lt;/p&gt;

&lt;p&gt;Very soon it was clear to us that we want to go with Next.js as it perfectly fits our needs. But now the question popped up: how should our backend look like? How should the API be connected with the Next.js application?&lt;/p&gt;

&lt;p&gt;We definitely don’t regret the decision of using Next.js. It is a great framework and in the last year we used it, there were major improvements without any bigger breaking changes - Vercel is doing a great job here.&lt;/p&gt;

&lt;p&gt;Just our solution of how we connected the backend to Next.js and the decision to host it on a private vServer and deploy it via Ansible wasn’t the best choice for us.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using a custom Express.js server
&lt;/h1&gt;

&lt;p&gt;So we dived deeper into the topic of how Next.js works and learned from the official docs that there is the possibility to &lt;a href="https://nextjs.org/docs/advanced-features/custom-server"&gt;use a custom Express.js server&lt;/a&gt; with Next.js where you have the whole freedom of leveraging the power as you would write a standalone &lt;a href="https://expressjs.com/"&gt;Express.js&lt;/a&gt; application. It just wraps around Next.js so that you can specify own routes and implement all other sorts of logic with Express.js and all other routes are handled normally by Next.js.&lt;/p&gt;

&lt;p&gt;It seemed like the best decision back then. We could move quickly, using a technology we already were familiar with and didn’t have to spin up a separate service for our backend. But for us there are some big cons to this approach which led us to the decision that we wouldn’t go this way anymore in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cons of the custom Express.js server
&lt;/h2&gt;

&lt;p&gt;With Next.js we were used to fast HMR which allowed us to develop quickly. We also wanted our backend to reload automatically when we do changes to our code so we used nodemon together with ts-node (because all the backend code is written in TypeScript too). The problem here: it wasn't fast anymore. Everytime we changed code in the backend the whole Express.js server rebooted and it took quite a while until it was running again. This also influenced some parts of our frontend, as it included shared files which were also detected by nodemon. We couldn't find a solution for this and it's actually pretty cumbersome.&lt;/p&gt;

&lt;p&gt;Additionally you're not able to deploy your Next.js application to Vercel anymore as they only provide deployments for plain Next.js applications. This also led us to custom deployment with Docker + Ansible on a vServer hosted by &lt;a href="https://netcup.de"&gt;netcup&lt;/a&gt; which we’re going into detail in a later section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Session handling and authentication
&lt;/h2&gt;

&lt;p&gt;Nevertheless it is how TrueQ is currently built. That means that the session handling and authentication is also completely handled by Express.js. For authentication we use &lt;a href="http://www.passportjs.org/"&gt;Passport.js&lt;/a&gt; which handles our normal E-Mail &amp;amp; Password login, but also third party logins via Google, GitHub and Twitter.&lt;/p&gt;

&lt;p&gt;In production we're using a &lt;a href="https://redis.io/"&gt;Redis&lt;/a&gt; server to persist the sessions (which also keeps them alive after the deployment of new versions).&lt;/p&gt;

&lt;h1&gt;
  
  
  GraphQL with Apollo
&lt;/h1&gt;

&lt;p&gt;Until then we were used to write REST APIs. We already heard about &lt;a href="https://graphql.org/"&gt;GraphQL&lt;/a&gt; here and then, but never got in touch with it. We got curious and got our hands dirty to spin up an API and see how we like it.&lt;/p&gt;

&lt;p&gt;We actually got hooked pretty fast. We love the flexibility to reuse DTOs, but at the same time only serve the fields you need for a specific usecase / view.&lt;/p&gt;

&lt;p&gt;We're using &lt;a href="https://www.apollographql.com/"&gt;Apollo&lt;/a&gt; both on the backend as server, but also on the frontend to query the API. Additionally we use &lt;a href="https://www.graphql-code-generator.com/"&gt;graphql-codegen&lt;/a&gt; to generate TypeScript models of our DTOs and the React hooks for Apollo. We're very happy with that setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem with calling the API logic directly when rendering on the serverside
&lt;/h2&gt;

&lt;p&gt;Now that we had an API in place we also needed to make sure that it's callable isomorphically. It should be reachable via the browser, when the Next.js application is in "SPA mode", but also on the server side when the HTML is being built for the first render.&lt;/p&gt;

&lt;p&gt;For the browser it is pretty straight forward. It just calls the &lt;code&gt;/api/graphql&lt;/code&gt; endpoint to execute queries and mutations. But on the serverside we thought that we somehow directly could execute the Apollo server logic. We didn't managed to get it running like this and that's why we need to do a seperate network request to &lt;code&gt;https://localhost:3000/api/graphql&lt;/code&gt; on the serverside, to also be able to make API calls there.&lt;/p&gt;

&lt;p&gt;All of this is wrapped in an Helper HoC which takes care of making the API calls isomorphic. Here's the code snippet of how we create the isomorphic Apollo link:&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;function&lt;/span&gt; &lt;span class="nx"&gt;createIsomorphLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpLink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apollo/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// eslint-disable-line @typescript-eslint/no-var-requires&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: we need to look into this, as with this we are still doing a network request to our own application, but with apollo-link-schema we don't have our context available on the serverside&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;HttpLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000/api/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;same-origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;fetch&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpLink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apollo/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// eslint-disable-line @typescript-eslint/no-var-requires&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;HttpLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;uri&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="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;same-origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;fetch&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;h1&gt;
  
  
  Knex.js + Objection.js in connection with Postgres
&lt;/h1&gt;

&lt;p&gt;So we had our API running and implemented the first CRUD operations. But where should the data be stored and be retrieved from? 😄&lt;/p&gt;

&lt;p&gt;As I said we already had some experience with Node.js applications back then, but we mostly worked with MongoDB + mongoose for accessing the database. In the last years being employed as software developer we enjoyed to work with relational databases and also thought that it would be a better fit for TrueQ. So we decided for PostgreSQL and searched for solutions how we could easily query our DB.&lt;/p&gt;

&lt;p&gt;Pretty soon we stumbled upon &lt;a href="http://knexjs.org/"&gt;Knex.js&lt;/a&gt;, a SQL query builder for Node.js. It takes care of the db connection (it also has support for pooling) and gives you the possibility to write SQL queries with a query builder pattern like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;knex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&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;author&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;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;books&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;h2&gt;
  
  
  Objection.js
&lt;/h2&gt;

&lt;p&gt;Knex.js even has support for strong typing with TypeScript, but during our research we found &lt;a href="https://vincit.github.io/objection.js/"&gt;Objection.js&lt;/a&gt;, an ORM which is built on top of Knex.js.&lt;/p&gt;

&lt;p&gt;It gives you the possibility to write Models and execute queries against them with type checking of all the available fields, we're actually pretty happy with it and back then we didn't know of a better solution for handling database access.&lt;/p&gt;

&lt;p&gt;Here you can see an example how a model plus a very simple query looks like. For more informations checkout their documentation.&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;Model&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;objection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Topic&lt;/span&gt; &lt;span class="kd"&gt;extends&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;id&lt;/span&gt;&lt;span class="o"&gt;!&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;name&lt;/span&gt;&lt;span class="o"&gt;!&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;static&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;topic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;findOne&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Topic&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;
  
  
  Running migrations and seeds
&lt;/h2&gt;

&lt;p&gt;Now when you're running an application productively there are also going to be database changes over the time. Therefore we also needed a solution to create migrations and run them in production.&lt;/p&gt;

&lt;p&gt;Luckily Knex.js also got us covered with this one. 🥳 In Knex.js each migration is a JavaScript file which exports an &lt;code&gt;up&lt;/code&gt; and a &lt;code&gt;down&lt;/code&gt; method to either execute the migration or roll it back. Before deploying a new version of TrueQ we just make sure to execute the latest migrations with the &lt;code&gt;knex migrate:latest&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Here is an example of the migration of our &lt;code&gt;question&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;knex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;knex&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;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;question&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;integer&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_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;notNullable&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;inTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trueq_user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;views&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;defaultTo&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="nx"&gt;notNullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deleted_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;down&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;knex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;knex&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;dropTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;question&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;Additionally Knex also supports &lt;a href="http://knexjs.org/#Seeds-CLI"&gt;Seeds&lt;/a&gt; for applying test data in your local environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look out for Prisma
&lt;/h2&gt;

&lt;p&gt;As already mentioned we actually were pretty happy with the Knex.js + Objection.js solution, but in the meanwhile we also found out about &lt;a href="https://www.prisma.io/"&gt;Prima&lt;/a&gt;. As it recently gained stable support for migrations we really consider to use it in our future applications, as it seems even more straight forward and better maintained.&lt;/p&gt;

&lt;h1&gt;
  
  
  Our Frontend
&lt;/h1&gt;

&lt;p&gt;After showing you the architecture of our backend, let's take a look at our frontend. As already mentioned we love React, we're writing React applications for a long time already and it is the frontend framework of our choice. Not sure if this still has to be mentioned for React projects created in 2020, but just to cover it: we only make use of functional components together with hooks 😄&lt;/p&gt;

&lt;p&gt;But as you might know, in the world of React you can pull in different libraries to solve things like routing or state management in your webapp, there are also some more things to talk about here.&lt;/p&gt;

&lt;h2&gt;
  
  
  State Management
&lt;/h2&gt;

&lt;p&gt;So we're using Apollo on the client side for fetching data from our GraphQL API. Apollo has a powerful caching mechanism built in which stores the results from your query and it also lets you update this cache manually for optimistic updates. That means for many cases the data is just stored in the Apollo cache.&lt;/p&gt;

&lt;p&gt;Additionally we also have quite some local logic, e.g. for our custom built editor. In those cases we're using &lt;a href="https://mobx.js.org/"&gt;MobX&lt;/a&gt; as a state management library. We love the simplicity which MobX gives you when defining state somewhere in your component tree, but at the same time taking care of only rerendering affected components down the path. It creates performant webapps by purpose.&lt;/p&gt;

&lt;p&gt;Maybe I am doing a more in-depth blog post about MobX in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  ChakraUI
&lt;/h2&gt;

&lt;p&gt;Of course we also needed a UI library, because we didn't want to write all sort of components on our own. Thanks to the above mentioned blog post we stumbled upon &lt;a href="https://chakra-ui.com/"&gt;ChakraUI&lt;/a&gt;, an accessible and modular UI library for React.&lt;/p&gt;

&lt;p&gt;For us ChakraUI is a bit different than other UI libraries. It simplified the way how we develop in the frontend and imho it complements the skills most web developers built up in the last years, which I describe more in detail with &lt;a href="https://dominik.sumer.dev/blog/chakra-complements-webdevs"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deployment
&lt;/h1&gt;

&lt;p&gt;In the summer of 2020 we came to the point that we already had quite a big part of TrueQ up and running. So we thought about how and where we're going to deploy our newly created webapp.&lt;/p&gt;

&lt;p&gt;Due to the custom Express.js app, Vercel wasn't an option straight from the beginning. Back then we absolutely had no knowledge about services like AWS or DigitalOcean. I just had my own vServer running for the Browsergame I developed several years ago. That's why I thought it would be the best idea to also host TrueQ on our own vServer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;So when we're building TrueQ to deploy it to our test or production environment, we're creating a docker container including the Next.js build output and all necessary files. This docker image is then pushed to our container registry on GitLab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ansible
&lt;/h2&gt;

&lt;p&gt;As we said we're deploying everything on a vServer on our own. That includes installing all necessary dependencies on the Linux server, configuring NGINX as our webserver, setting up SSL certificates, setting up the database, ensuring backups, and so on.&lt;/p&gt;

&lt;p&gt;Because we didn’t just want to set this up by hand, we chose Ansible as our operator here. With Ansible you can create playbooks which get executed step by step as an automated way to setup your server. You just tell Ansible what to do in it's own DSL written in yaml files. That means that if for whatever reason we need to setup a new server, we just need to execute the Ansible playbook there and the Server would be up and running with TrueQ.&lt;/p&gt;

&lt;p&gt;With Ansible it's also much simpler to keep track of the changes you're doing on your server, as all of the playbook files are also versioned via git.&lt;/p&gt;

&lt;p&gt;In retrospective we learned very much about this whole process. But one thing we definitely learned is that we don't want to do this stuff on our own anymore. 😅 More about this in the last section of this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics with the ELK stack
&lt;/h2&gt;

&lt;p&gt;For the analytics we're using the &lt;a href="https://www.elastic.co/"&gt;ELK stack&lt;/a&gt; also hosted on those vServers. We're collecting logs via filebeat and metrics with metricbeat. Additionally we're having the Kibana APM in place to get even more insights from our Node.js backend application.&lt;/p&gt;

&lt;p&gt;Also this was a very interesting step, but again too much hassle to maintain this on our own.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab CI/CD
&lt;/h2&gt;

&lt;p&gt;The process of building a docker image and deploying it with Ansible is all encapsulated in mostly automated steps via our GitLab pipelines.&lt;/p&gt;

&lt;p&gt;Everytime we’re merging changes into the master branch, our whole test suite is being executed and if everything succeeds, a new version of TrueQ is being built (the Docker image) und pushed to the image registry on GitLab.&lt;/p&gt;

&lt;p&gt;Every night we’re deploying the latest version of our Docker image to our test environment automatically and additionally there is a separate schedule for deploying the latest version to production which can only be executed manually.&lt;/p&gt;

&lt;p&gt;During this deployment, we’re executing an Ansible role, which connects to the Server, pulls the latest Docker image there and spins it up.&lt;/p&gt;

&lt;p&gt;Here you can see the GitLab stage configuration for deploying trueq:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy_trueq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_trueq&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ansible --version&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo $ANSIBLE_VAULT_PASS &amp;gt;&amp;gt; .vault-pass&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ansible-playbook -i "inventory/$ANSIBLE_INVENTORY" main.yml --tags=trueq --vault-password-file=.vault-pass&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rm .vault-pass&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$ANSIBLE_INVENTORY != &lt;/span&gt;&lt;span class="no"&gt;null&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$DEPLOY_TRUEQ == "true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Test Setup
&lt;/h1&gt;

&lt;p&gt;So we now covered the stack we used for developing TrueQ and also how we're deploying it. Now comes a topic which we actually began with pretty soon: writing tests.&lt;/p&gt;

&lt;p&gt;TDD is an acronym (standing for Test Driven Development) we heard pretty often in our career before, but never did it by our own. We wanted to give it a try, at least for our backend, and boy was this a good idea. 😄 Every time we began with new features, we created test cases for all the requirements and edge cases we could think of in &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt;. And before writing the actual implementation we started to write the failing tests including the assertions of how things should work. After the implementation was done those test should get executed successfully.&lt;/p&gt;

&lt;p&gt;It saved us many possible bugs and currently about 80% of our APIs are covered with tests (mostly integration tests) which give us the confidence for larger refactorings and other future changes. The &lt;a href="https://testingjavascript.com/"&gt;Testing Javascript&lt;/a&gt; course by Kent C. Dodds definitely was a huge help with creating our test setup and learning some unknown things about Jest.&lt;/p&gt;

&lt;p&gt;In the future we also consider using &lt;a href="https://www.cypress.io/"&gt;cypress&lt;/a&gt; for e2e tests to gain even more confidence.&lt;/p&gt;

&lt;h1&gt;
  
  
  What would you do differently nowadays?
&lt;/h1&gt;

&lt;p&gt;Let’s come to an end. And in the end it’s always time for the most interesting question which probably is: what would we do differently in future products?&lt;/p&gt;

&lt;p&gt;As already explained in the previous sections the top things we’re complaining about are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the custom Express.js server for Next.js&lt;/li&gt;
&lt;li&gt;the deployment which we don’t want to handle on our own anymore&lt;/li&gt;
&lt;li&gt;maybe using Prism instead of Knex.js or another solution for accessing the database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be honest we’re not completely sure how our future tech stack is going to look like exactly. &lt;/p&gt;

&lt;p&gt;For the frontend we’re already very happy and definitely going to stay with React, Next.js and Chakra. Maybe we’re going to switch the Apollo client with &lt;a href="https://react-query.tanstack.com/"&gt;React Query&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the backend there will probably be more changes and depending on what we go with, it will also affect database access and hosting. We’re looking closely at &lt;a href="https://blitzjs.com/"&gt;Blitz.js&lt;/a&gt; and &lt;a href="https://supabase.io/"&gt;Supabase&lt;/a&gt; and consider deploying on AWS, Vercel, &lt;a href="https://www.digitalocean.com/"&gt;DigitalOcean&lt;/a&gt; or &lt;a href="https://render.com/"&gt;Render&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We're very glad that the first MVP of our new product &lt;a href="https://snappify.io"&gt;snappify&lt;/a&gt; comes with barely any backend logic and is completely hosted on Vercel, but soon we need a more sophisticated solution and I am going to inform you how our updated tech stack looks like as soon as we’ve settled. ✌️&lt;/p&gt;

</description>
      <category>saas</category>
      <category>typescript</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
    <item>
      <title>How and why to start blogging</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Tue, 16 Feb 2021 18:17:03 +0000</pubDate>
      <link>https://dev.to/dominiksumer/how-and-why-to-start-blogging-2knb</link>
      <guid>https://dev.to/dominiksumer/how-and-why-to-start-blogging-2knb</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/how-and-why-to-start-blogging"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Actually I had the plan that this will be the first blog post on my newly launched website. But then I thought it’s maybe a bit ironic, that my first blogpost after years of not-blogging, would be about how to start blogging. 😅&lt;br&gt;
Nevertheless, before starting to blog again I thought a lot about the topic and want to share with you my findings on how to start blogging, why to do it and what to consider when doing so.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did you ever take a seat and just write down your thoughts about a topic? It can really pull you in. You create your own little story around it and create something unique which will additionally help a lot of other people.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  🧐 Pick a topic which you’re passionate about
&lt;/h1&gt;

&lt;p&gt;So you want to start with a blog, but don’t know which topic(s) you should write about? For me it works the best to choose a topic which I have a great passion for.&lt;/p&gt;

&lt;p&gt;If you choose a topic just because it is trending and you want to achieve many clicks for it, it probably won’t work because your interest in this topic won’t be high and therefore your motivation to write a good blog post about it will fluctuate. (at least in the long run)&lt;/p&gt;

&lt;p&gt;So in my example, most of the time I am going to be blogging about software development, or to be more specific: web development, because this is where my passion lies in. I also want to share more about my journey and build / learn in public.&lt;/p&gt;

&lt;p&gt;Think about what bothers you the most or where you recently put much brain-power into. Then write your little story about it and share it!&lt;/p&gt;

&lt;h1&gt;
  
  
  📝 Create drafts
&lt;/h1&gt;

&lt;p&gt;A great tip for gathering ideas for new blog posts is to create drafts (or just write down a few words) immediately after you experienced something.&lt;/p&gt;

&lt;p&gt;When it comes to programming, for me it’s often like this: I stumble about a problem and deliberately search for a solution, but as soon as I’ve got the solution, I just move on with the next task and forget about the problem pretty fast. What if you put the „sharing“ as part of your problem solving? Your problem is only really solved if you write down some words about it and save it as a draft. And sometimes later, you might add some more content to it and write a blog post.&lt;/p&gt;

&lt;p&gt;But actually this could be applied to every daily activity. You experience something new? Write down a draft or only some words and later work out a more detailed blog post from it.&lt;/p&gt;

&lt;h1&gt;
  
  
  🤔 Is this really shareable?
&lt;/h1&gt;

&lt;p&gt;Now you have chosen a topic which fits perfectly but you ask yourself: is this really shareable? Does someone even benefit from my blog post or don't there already exist several blog posts about it?&lt;/p&gt;

&lt;p&gt;Long story short: someone will definitely benefit from your blog posts. There might be similar blog posts to the same topic, but your findings, your writing style and how you describe it is unique. There is definitely someone out there who is currently in the same process as you were before writing your blog post and would be really thankful for it.&lt;/p&gt;

&lt;p&gt;So whenever you’ve achieved something and your mind tells you: &lt;em&gt;"mhh this can be done by anyone, I am sure no one would benefit from it"&lt;/em&gt;, then give your mind a kick and jump into the cold water! You'll also gain many benefits by doing so. 🙂&lt;/p&gt;

&lt;h1&gt;
  
  
  🌱 Why should I share my knowledge?
&lt;/h1&gt;

&lt;p&gt;You have a topic and know it &lt;strong&gt;is&lt;/strong&gt; shareable, but what benefits do you have from sharing it? Why give some of your knowledge to the public for free? Lets look at all the benefits blogging brings us.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Improve your writing skills
&lt;/h2&gt;

&lt;p&gt;Having good writing skills can help you in many areas. May it be to write professional emails, describe yourself in a resumé or when writing documentation.&lt;/p&gt;

&lt;p&gt;If you start blogging and writing down your thoughts about your experiences, you will see that those writing skills will greatly improve and that it will get easier over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Gain an even deeper knowledge about the topic
&lt;/h2&gt;

&lt;p&gt;Writing down your thoughts will strengthen your knowledge!&lt;/p&gt;

&lt;p&gt;By formulating your achievement into words and creating a small story around it you will remember it better in the future. And if you publish it as a blog post you can refer to it in conversations.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make yourself more visible
&lt;/h2&gt;

&lt;p&gt;If your blog is publicly available it will make yourself more visible. Don't underestimate the power of this fact. By sharing your knowledge in a specific area you will gain credibility in this field and will also make new connections which could bring up opportunities you never would've thought of.&lt;/p&gt;




&lt;p&gt;I guess there are even many more benefits when it comes to blogging, which I didn't list above. So be sure that writing and publishing blog posts isn't just a waste of time.&lt;/p&gt;

&lt;h1&gt;
  
  
  ☕️ Motivate yourself for writing
&lt;/h1&gt;

&lt;p&gt;Ok so now comes the time where you say: &lt;em&gt;I see the benefits, I have a topic, but I don’t want to write something.&lt;/em&gt;&lt;br&gt;
I must admit, you probably need to have fun with writing up some content. Otherwise it won’t be a sustainable thing you do over a longer period of time. But have you ever tried it?&lt;/p&gt;

&lt;p&gt;Did you ever take a seat and just write down your thoughts about a topic? It can really pull you in. You create your own little story around it and create something unique which will additionally help a lot of other people.&lt;/p&gt;

&lt;p&gt;If writing content is not already a big part of your daily job it can really be relaxing to just take some time, let your thoughts flow and write them down. Our society is built up to rush from one stressy moment into the other. Calming down, really think of what you've experienced and form it into words is a great way to spend your time.&lt;/p&gt;

&lt;p&gt;I would suggest to just try it out and see if you like it.&lt;/p&gt;

&lt;h1&gt;
  
  
  💻 Which technologies should I use to create my blog?
&lt;/h1&gt;

&lt;p&gt;Of course you also need a place for your blog. There are already well-known and stable platforms like &lt;a href="https://wordpress.com"&gt;wordpress.com&lt;/a&gt;, &lt;a href="https://dev.to"&gt;dev.to&lt;/a&gt;, &lt;a href="https://hashnode.dev"&gt;hashnode.dev&lt;/a&gt; or &lt;a href="https://medium.com"&gt;medium.com&lt;/a&gt; which you can use to create outstanding blog posts.&lt;/p&gt;

&lt;p&gt;If you are a web developer, you probably also play with the thought to just creating your own blog with the technologies you're familiar with.&lt;/p&gt;

&lt;p&gt;In the end you should choose the solution which you're feeling most comfortable with. I had fun creating my new website + blog from scratch, but it can also be overwhelming when you want to create the perfect experience before starting to blog.&lt;/p&gt;

&lt;h1&gt;
  
  
  🙌 Don’t overthink it
&lt;/h1&gt;

&lt;p&gt;Ok now comes probably the most important tip:&lt;br&gt;
&lt;strong&gt;Don't. Overthink. It.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The biggest enemy of your blogging journey is your mind. When you again start to overthink, when you want to be prepared for every situation, want to do everything right in the beginning, it can end with demotivation pretty fast.&lt;/p&gt;

&lt;p&gt;To avoid this, I found it helpful to have a checklist which kept only the things that are essential for my blog before launching it (and some gimmicks I had fun implementing). Additionally I made a seperate list with things I want to have implemented for my blog in the long run.&lt;/p&gt;

&lt;p&gt;But before showing you this checklist for my individual example, I want to mention that what I consider a must have, doesn’t need to be a must have for you. Only put things on your checklist which &lt;strong&gt;you&lt;/strong&gt; think are necessary to have done upfront. The other stuff can also be done over time when you’ve already started your blogging journey. Don’t let any bureaucracies demotivate you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Must haves
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Setup Analytics for better insights about visitors&lt;/li&gt;
&lt;li&gt;Make sure to set a canonical url&lt;/li&gt;
&lt;li&gt;Setup Google search console&lt;/li&gt;
&lt;li&gt;Create a sitemap.xml to be crawled by Google&lt;/li&gt;
&lt;li&gt;Newsletter and author section at the bottom of every blog post&lt;/li&gt;
&lt;li&gt;Configure open graph tags properly&lt;/li&gt;
&lt;li&gt;Create a nice open graph image&lt;/li&gt;
&lt;li&gt;Nice design for code snippets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nice to haves
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Setup an email subscription list with ConvertKit&lt;/li&gt;
&lt;li&gt;Provide a RSS feed&lt;/li&gt;
&lt;li&gt;Add Share Buttons&lt;/li&gt;
&lt;li&gt;Progress bar while scrolling&lt;/li&gt;
&lt;li&gt;Copy functionality for code snippets&lt;/li&gt;
&lt;li&gt;Maybe integrate with a CMS for better handling - still unsure about this&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  🤠 Get inspirations from other blogs
&lt;/h1&gt;

&lt;p&gt;While working on something new, it’s always good to look at other people in the field and see how they are doing it. It doesn't matter if you're now building the blog on your own or if you use a familiar platform: you can get great inspirations for your blog by looking on other blogs out there.&lt;/p&gt;

&lt;p&gt;I don't mean you should copy and paste from another blog, just get yourself some inspirations to begin with. Over the time your blog will develop further and get your own touch.&lt;/p&gt;

&lt;p&gt;Here are some blogs I really like and took inspirations from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://phiilu.com/"&gt;phiilu.com&lt;/a&gt; - Florian's blog has smooth page transitions, all features you need for a blog and at the same time very informative posts 🤩&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rhnmht30.dev/"&gt;rhnmht30.dev&lt;/a&gt; - I love the sleek design of Rohan's blog and wanted to keep the design of mine as simple as his 🙂&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://danielwirtz.com/"&gt;danielwirtz.com&lt;/a&gt; - I also like the clean design of Daniel's blog and took that as an inspiration for my blog ✌️&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://samuelkraft.com/"&gt;samuelkraft.com&lt;/a&gt; - Recently I stumbled upon Samuel's blog. It's neatly crafted by hand and his blog posts are awesome too! 🔥&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  📖 Great resources with tips and tricks
&lt;/h1&gt;

&lt;p&gt;It's time to come to an end with this blog post. Hopefully some of those tips helped you to start with your blogging journey. You can also send me your blogs, I am excited to see them - &lt;a href="https://twitter.com/messages/compose?recipient_id=798465058061881344"&gt;my Twitter DMs are open&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Last but not least I want to share some great resources which helped me while starting to blog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free &lt;a href="https://bloggingfordevs.com/"&gt;Blogging for devs newsletter&lt;/a&gt; by &lt;a href="https://twitter.com/monicalent"&gt;@monicalent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.swyx.io/learn-in-public/"&gt;A great blog post&lt;/a&gt; about Learn in public by &lt;a href="https://twitter.com/swyx"&gt;@swyx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browsing through Twitter's &lt;a href="https://twitter.com/search?q=%23learninpublic"&gt;#learninpublic&lt;/a&gt; or &lt;a href="https://twitter.com/search?q=%23buildinpublic"&gt;#buildinpublic&lt;/a&gt; hashtags to find some inspiring blogs&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devjournal</category>
    </item>
    <item>
      <title>A guide to make your Stripe Checkout EU VAT compliant</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Sun, 31 Jan 2021 16:44:58 +0000</pubDate>
      <link>https://dev.to/dominiksumer/a-guide-to-make-your-stripe-checkout-eu-vat-compliant-21m2</link>
      <guid>https://dev.to/dominiksumer/a-guide-to-make-your-stripe-checkout-eu-vat-compliant-21m2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;As Stripe released their new Tax feature, this blog post is probably deprecated for most of us. Checkout &lt;a href="https://stripe.com/at/tax" rel="noopener noreferrer"&gt;Stripe Tax&lt;/a&gt; to make your life a lot easier. :)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/stripe-checkout-eu-vat" rel="noopener noreferrer"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You’re located in the EU, want to bring your SaaS (or any other digital product) to customers over the whole world and are not sure what you exactly need to do? You’re implementing the payment process with Stripe Checkout but don’t know what exactly needs to be done in order to be EU VAT compliant? I’ve gone through the same and with this blog post I want to give you a step-by-step guide to make your Stripe payment process EU VAT compliant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to state that I am no accountant, financial advisor or anything like that. I can’t guarantee to keep this information up-to-date or that it really applies to your specific situation in your country. Please check back with your accountant before going live with a payment process to be sure everything works as expected and you don’t need to worry about illegal activities or large payments to the tax office.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  What is the EU VAT
&lt;/h1&gt;

&lt;p&gt;The term VAT stands for Value Added Tax which is applied to basically all goods and services that are bought and sold for use or consumption in the European Union. So it also applies to digital productions and you have to ensure to correctly collect and pay the VAT. Every EU country has its own VAT percentage and &lt;a href="https://ec.europa.eu/taxation_customs/business/vat/telecommunications-broadcasting-electronic-services/vat-rates_en" rel="noopener noreferrer"&gt;you can find them here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  When and how much VAT do you need to collect
&lt;/h1&gt;

&lt;p&gt;I assume you have a business registered in the EU and you want to sell your product to customers all over the world. There are four different scenarios depending on your country, the country of the customer and if you sell to a business or private customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Selling to customers outside the EU 🪶
&lt;/h2&gt;

&lt;p&gt;If your customer is located outside the EU, independent if it’s a business or private customer, no VAT has to be charged.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Selling to customers in your home country 🤝
&lt;/h2&gt;

&lt;p&gt;If your customer is located in your home country, you have to charge the corresponding VAT percentage of your home country and pay it to the tax office.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Selling to EU businesses outside your home country ✌️
&lt;/h2&gt;

&lt;p&gt;If it is a business customer located outside your home country you don’t have to collect a VAT, as reverse charge applies.&lt;/p&gt;

&lt;p&gt;But you definitely have to make sure to collect the VAT number of the business and its billing address. You also have to validate the VAT number and ensure that the business is valid and really exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Selling to private EU customers outside your home country 🤔
&lt;/h2&gt;

&lt;p&gt;Last and least, the most cumbersome case. For private EU customers outside your home country you have to collect the corresponding VAT of the customers country. You also need to pay it to the corresponding tax office.&lt;/p&gt;

&lt;p&gt;You also need to save/verify the location of the customer with the billing and IP address for future proof.&lt;/p&gt;

&lt;p&gt;Hint: it can be easier based on your yearly turnover with &lt;a href="https://ec.europa.eu/taxation_customs/business/vat/telecommunications-broadcasting-electronic-services/content/declare-and-pay-vat-moss_en" rel="noopener noreferrer"&gt;MOSS&lt;/a&gt;. But I didn’t dive into this topic.&lt;/p&gt;

&lt;h1&gt;
  
  
  Stripe Checkout
&lt;/h1&gt;

&lt;p&gt;Ok now that we covered the different cases, let’s move on with the Stripe Integration and make sure to handle everything correctly in our Stripe Checkout Session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;I assume you already have the Stripe Checkout set up correctly as I won't go through the process itself in detail. If not I can recommend the official guide directly from the &lt;a href="https://stripe.com/docs/billing/subscriptions/checkout" rel="noopener noreferrer"&gt;Stripe Docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage a list of countries
&lt;/h2&gt;

&lt;p&gt;As I am currently writing this blog post, Stripe doesn’t automatically take care of managing available countries and their VAT percentage. So you have to do it on your own.&lt;/p&gt;

&lt;p&gt;As list of countries I can recommend you &lt;a href="https://gist.github.com/keeguon/2310008" rel="noopener noreferrer"&gt;this public json file&lt;/a&gt; which includes basically all countries over the world. Then you can extend all the EU countries with two fields: isEU and vatPercentage, which tells us that the country is a member of the EU and how much the corresponding VAT percentage is.&lt;/p&gt;

&lt;p&gt;Here an example list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const COUNTRIES = [
  { name: 'United States', code: 'US' },
  ...
  // WARNING these values are not being maintained
  { name: 'Austria', code: 'AT', isEU: true, vatPercentage: 20 },
  { name: 'Belgium', code: 'BE', isEU: true, vatPercentage: 21 },
  { name: 'Bulgaria', code: 'BG', isEU: true, vatPercentage: 20 },
  { name: 'Croatia', code: 'HR', isEU: true, vatPercentage: 25 },
  { name: 'Cyprus', code: 'CY', isEU: true, vatPercentage: 19 },
  ...
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Update the VAT taxes via the Stripe API
&lt;/h2&gt;

&lt;p&gt;As we now have a list of all EU countries and their corresponding VAT percentage, we need to keep them up-to-date in our Stripe account. Stripe doesn't take care of managing those values, we have to do it on our own and the most straight forward way to do this is via the &lt;a href="https://stripe.com/docs/api/tax_rates" rel="noopener noreferrer"&gt;Stripe Tax Rates API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before I show you the logic I'm using for keeping those taxes up-to-date I want to explain the process a bit. Basically I store all EU tax rate in a table in the DB of my application. The DB Model looks 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="k"&gt;import&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;objection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaxRateModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="o"&gt;!&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;stripeId&lt;/span&gt;&lt;span class="o"&gt;!&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;vatPercentage&lt;/span&gt;&lt;span class="o"&gt;!&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="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tax_rate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;countryCode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when my application boots up, I am looping through all EU countries from the list, and if they don't exist, I send them to Stripe via the API and save them in the DB. If they already exist in the DB, I know that I've already sent them to Stripe and just check if the VAT percentage differs. If it differs, I tell Stripe that the old Tax Rate is deprecated and create a new one. (Updating the VAT percentage is not possible, to keep history of older payments intact)&lt;/p&gt;

&lt;p&gt;With this we can use the country list to manage the VAT percentages of all EU countries and keep them automatically updated in Stripe. 🎉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createOrUpdateVatTaxRates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EU_COUNTRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;COUNTRIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEU&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;EU_COUNTRIES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingTaxRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TaxRateModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// we only move on if no taxRate exists yet in our DB or the vatPercentage differs&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;existingTaxRate&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;existingTaxRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vatPercentage&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vatPercentage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TaxRate changed for country: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// either way we're creating a new taxRate via the Stripe API&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VAT &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;country&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="na"&gt;inclusive&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;percentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vatPercentage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingTaxRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// if there is already an existing tax rate, we set the old one to inactive via the Stripe API&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingTaxRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;active&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;// and we update the tax rate in our DB with the new vatPercentage and stripeId&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;existingTaxRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;stripeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;vatPercentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vatPercentage&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TaxRateModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;stripeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;vatPercentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vatPercentage&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="nf"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Collect the customers billing address
&lt;/h2&gt;

&lt;p&gt;Now that we have all tax rates available in Stripe, let's move on with the first step in our payment process to ensure we charge the correct VAT rate.&lt;/p&gt;

&lt;p&gt;The form I am using to collect the customers billing address looks like this:&lt;/p&gt;

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

&lt;p&gt;Here we collect all necessary data in order to check if the user is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;located in our home country&lt;/li&gt;
&lt;li&gt;located in the EU&lt;/li&gt;
&lt;li&gt;is a business customer or not (and collect the VAT number of the company)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So before we start the Stripe Checkout session, we upsert the customer via the Stripe API and can already tell if the customer has to pay the VAT normally, if reverse charge applies or if we don't have to charge any VAT, because the customer is not located in the EU. Here is the corresponding code for upserting the customer:&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;function&lt;/span&gt; &lt;span class="nf"&gt;mapUserToStripeCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isEUCustomer&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;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CustomerUpdateParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// our business is located in Austria, so we have to check if the customer is also from Austria&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HOME_COUNTRY_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;taxExempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CustomerUpdateParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TaxExempt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isEUCustomer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingVatNumber&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingCountryCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;HOME_COUNTRY_CODE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// if it is a country in the EU and not from austria, reverse charge applies&lt;/span&gt;
      &lt;span class="nx"&gt;taxExempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reverse&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// b2c in EU or business from our home country, VAT applies normally&lt;/span&gt;
      &lt;span class="nx"&gt;taxExempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// customer from outside the EU, no VAT applies&lt;/span&gt;
    &lt;span class="nx"&gt;taxExempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exempt&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingEmail&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingCity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingCountryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;line1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingStreet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;postal_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingZip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;tax_exempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taxExempt&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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;upsertStripeCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedVatNumber&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userCountry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;COUNTRIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&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="nx"&gt;billingCountryCode&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;isEUCustomer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;userCountry&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;isEU&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;baseData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapUserToStripeCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isEUCustomer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if our user is already existing in Stripe, we update his record&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;isEUCustomer&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;updatedVatNumber&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingVatNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// we create a new tax id (VAT number) for the customer if he changed it&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTaxId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu_vat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingVatNumber&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// update the record via the Stripe API&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseData&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// create a new customer record via the Stripe API&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tax_id_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;isEUCustomer&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingVatNumber&lt;/span&gt; &lt;span class="p"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu_vat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingVatNumber&lt;/span&gt; &lt;span class="p"&gt;}]&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="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;trueqId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="c1"&gt;// assign the stripeCustomerId to our user&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error updating the stripe customer id (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) of the user with id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="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;As you can see we're also telling Stripe the VAT number of the customer. Stripe will automatically check if the VAT number is in a correct format, if not it will immediately throw an error with the code &lt;code&gt;tax_id_invalid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Stripe will also check in the background, if it is a valid VAT number (or if e.g. the company doesn't exist anymore). You will receive an event from stripe called &lt;code&gt;customer.tax_id.updated&lt;/code&gt; so you can manually check if your customer has provided a valid VAT number and if it matches his billing address.&lt;/p&gt;

&lt;p&gt;Be aware! Stripe only validates the VAT number initially. If you have recurring payments you actually need to check on your own if the VAT number is still valid for future invoices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pass the correct tax rate to the Stripe Checkout Session
&lt;/h2&gt;

&lt;p&gt;As we have the billing data collected from our customer and told Stripe about the tax situation of the customer, let's move on with the creation of the Stripe Checkout session and specify &lt;em&gt;which&lt;/em&gt; VAT rate the customer has to pay.&lt;/p&gt;

&lt;p&gt;For this we only need to get the country of the customers billing address, fetch the corresponding tax rate from our database and get its Stripe ID. This can then be passed to the Stripe Checkout API Call and everything should work as expected. 🥳&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hint: you don't have to check if the customer is a business customer, because we set the &lt;code&gt;tax_exempt&lt;/code&gt; property of the customer to &lt;code&gt;reverse&lt;/code&gt;, so Stripe makes sure that he isn't charged with VAT&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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createStripeCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stripePriceId&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;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userCountry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;COUNTRIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&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="nx"&gt;billingCountryCode&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;isEUCustomer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;userCountry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEU&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaxRateModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isEUCustomer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TaxRateModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userCountry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subscription&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;payment_method_types&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;card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;line_items&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="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stripePriceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// here we pass the VAT rate if one applies&lt;/span&gt;
          &lt;span class="na"&gt;tax_rates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeId&lt;/span&gt;&lt;span class="p"&gt;]&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="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;allow_promotion_codes&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;success_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;applicationUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;stripe/success?session_id={CHECKOUT_SESSION_ID}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cancel_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;applicationUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;profile/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/billing`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;When the customer proceeds to the Stripe Checkout (and is going to be charged with VAT), he will already see how much VAT is going to be charged.&lt;/p&gt;

&lt;p&gt;The invoices you're obtaining from Stripe will now of course also contain the corresponding VAT you've charged.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;We're finished! 🎉&lt;/p&gt;

&lt;p&gt;Let's recap what we've done:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;managing a list of EU countries and there VAT percentage&lt;/li&gt;
&lt;li&gt;make sure to keep the Tax Rates up-to-date via the Stripe API&lt;/li&gt;
&lt;li&gt;collect the billing address of the customer, validate the VAT number of a company and tell Stripe if VAT, reverse charge or nothing should be applied&lt;/li&gt;
&lt;li&gt;pass the correct tax rate to the Stripe Checkout API&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;What you still should take care of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check if the VAT number of a company is valid and matches with the billing address&lt;/li&gt;
&lt;li&gt;verify the VAT number also for recurring payments&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I hope you enjoyed this guide and you could get some helpful insights!&lt;/p&gt;

&lt;p&gt;I've spent some time over the last weeks to get more insights into Stripe which aren't covered in this blog post. If you have any further questions, I am happy to help out. I would suggest to &lt;a href="https://trueq.io/t/stripe" rel="noopener noreferrer"&gt;ask a question&lt;/a&gt; in the corresponding section of our developer community TrueQ, where I'm subscribed and get notified about new questions. ✌️&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe is working on an easier solution
&lt;/h2&gt;

&lt;p&gt;The Stripe support team contacted me and said that they're working on a better solution for this. Chances are high that they calculate the taxes based on our home country and we don't have to do this on our own anymore.&lt;/p&gt;

&lt;p&gt;I am excited and looking forward to future changes by Stripe, until then I hope some of you can find some value in this blog post.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>saas</category>
      <category>stripe</category>
      <category>vat</category>
    </item>
    <item>
      <title>How ChakraUI complements your existing web development skills</title>
      <dc:creator>Dominik Sumer</dc:creator>
      <pubDate>Sun, 10 Jan 2021 18:44:00 +0000</pubDate>
      <link>https://dev.to/dominiksumer/how-chakraui-complements-your-existing-web-development-skills-gg7</link>
      <guid>https://dev.to/dominiksumer/how-chakraui-complements-your-existing-web-development-skills-gg7</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on my &lt;a href="https://dominik.sumer.dev/blog/chakra-complements-webdevs"&gt;personal website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Over the last years many different React component libraries got released. For example &lt;a href="https://ant.design"&gt;Ant Design&lt;/a&gt;, &lt;a href="https://react-bootstrap.github.io/"&gt;React Bootstrap&lt;/a&gt;, &lt;a href="https://material-ui.com/"&gt;MaterialUI&lt;/a&gt;. They all battle-powered us with components we‘ve needed in day-to-day UI development, but none of them changed the way I develop as ChakraUI did.&lt;/p&gt;

&lt;p&gt;Chakra brings new paradigms on the plate and at the same time complements the skills you've built up over the last 10 years as a web developer.&lt;/p&gt;

&lt;h1&gt;
  
  
  CSS as you know it
&lt;/h1&gt;

&lt;p&gt;Do you know CSS? Oh well, then you already know how to develop with ChakraUI. When you’re styling components you’re able to style them with props which map 1:1 to existing CSS properties as you know them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;Box&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;@chakra-ui/react&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;YourComponent&lt;/span&gt; &lt;span class="o"&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="nc"&gt;Box&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1px solid black"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    Hello World!
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you’re applying those properties to your components, ChakraUI takes care of generating CSS classes containing those styles. Chakra provides you different components and you're able to apply those styles to basically all of them. Of course some are restricted to specific types (e.g. flexbox properties to the &lt;code&gt;Flex&lt;/code&gt; component).&lt;/p&gt;

&lt;p&gt;In addition to all known CSS properties, ChakraUI gives you some abbreviations which map to CSS properties in order to increase readability of your code (as soon as you know what they stand for). For example &lt;code&gt;w&lt;/code&gt; maps to &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;m&lt;/code&gt; maps to &lt;code&gt;margin&lt;/code&gt;, &lt;code&gt;pos&lt;/code&gt; maps to &lt;code&gt;position&lt;/code&gt; and so on. Have a look at the &lt;a href="https://chakra-ui.com/docs/features/style-props"&gt;list of all available style props&lt;/a&gt; in Chakra.&lt;/p&gt;

&lt;h1&gt;
  
  
  Approachable CSS-in-JS
&lt;/h1&gt;

&lt;p&gt;To be honest I never felt comfortable with existing CSS-in-JS solutions and always ended writing &lt;code&gt;less&lt;/code&gt; files which held the styles for the corresponding components. I don't think that it's only the fault of those solutions as I also was lazy diving deep into them and seeing the benefits. Nevertheless: Chakra changed this.&lt;/p&gt;

&lt;p&gt;With it’s composable nature it’s a breeze to either just style single elements in your component, or creating new components which extend the styles of another one. Your freedom is endless.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pseudo Styling
&lt;/h1&gt;

&lt;p&gt;Ok now you maybe think: what's with pseudo styles? You want to style the hover or focused state of your desired element, so what? Chakra of course also backs you up on this one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt; &lt;span class="na"&gt;_hover&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;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red.500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello World!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With properties like &lt;code&gt;_hover&lt;/code&gt; or &lt;code&gt;_focus&lt;/code&gt; you can style the corresponding state with the above described styling props. The documentation for pseudo styles can be found &lt;a href="https://chakra-ui.com/docs/features/style-props#pseudo"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition Chakra even provides you basic features for using pseudo styling in the case of grouped elements. It doesn't cover all usecases (e.g. nested groups) but is a great companion most of the time and keeps your code clean and simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt; &lt;span class="na"&gt;_hover&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;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;semibold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;_groupHover&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;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tomato&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Responsive Styles
&lt;/h1&gt;

&lt;p&gt;In the past years the "mobile first" approach got a lot of attentation. Legitimately as many users are surfing the web with different device sizes. So how does Chakra helps us with this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt; &lt;span class="na"&gt;m&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;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sm&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="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello World!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On most of the styling props you're able to specify values for different device sizes. Chakra also follows the mobe first approach, because the style for the smallest specified breakpoint will also apply to all breakpoints above.&lt;/p&gt;

&lt;p&gt;Chakra comes with its &lt;a href="https://chakra-ui.com/docs/features/responsive-styles"&gt;default defined breakpoints&lt;/a&gt;, but you can adjust them to fulfill your needs.&lt;/p&gt;

&lt;p&gt;As a shorthand method, you can also pass it as an array with ascending values. This may reduce code, but also can decrease readablity and removes the possibility to leave out breakpoints in between. Here is the same code as above, written with the shorthand method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt; &lt;span class="na"&gt;m&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="mi"&gt;2&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="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello World!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Flexible Theming
&lt;/h1&gt;

&lt;p&gt;As we've just talked about adjusted breakpoints, let's also talk about adjusting other stuff. Every component library has the problem, that as it grows in popularity, it's design gets worn out. I guess everyone of us remembers when every third webapp had the standard bootstrap look. Luckily you can adjust the look and feel of Chakra heavily with it's flexible theming approach.&lt;/p&gt;

&lt;p&gt;Here is the code snippet how I am adjusting the default Chakra theme for this blog:&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;ButtonProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extendTheme&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;@chakra-ui/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mode&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;@chakra-ui/theme-tools&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;ButtonNavVariant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&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="na"&gt;_hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blackAlpha.200&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;whiteAlpha.200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;textDecor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;extendTheme&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;scrollBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smooth&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html, body&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;overflowX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;fontSizes&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;3xl&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;2em&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;lineHeights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;shorter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.2em&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;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonNavVariant&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="na"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonNavVariant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;customTheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see you're not only able to apply global styles with this approach, you can also adjust other stuff like default &lt;code&gt;fontSizes&lt;/code&gt;, &lt;code&gt;lineHeights&lt;/code&gt; and also styles for internal Chakra components. I really love how flexible it is.&lt;/p&gt;

&lt;p&gt;Checkout the &lt;a href="https://chakra-ui.com/docs/theming/theme"&gt;documentation of the default theme&lt;/a&gt; and all &lt;a href="https://chakra-ui.com/docs/theming/customize-theme"&gt;possibilities for customization&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chakra Factory
&lt;/h1&gt;

&lt;p&gt;Ok so it's cool to change the default look of a Chakra component, but what if you don't want to change the default style of a component, but create a new component with some adjustments to the original one?&lt;/p&gt;

&lt;p&gt;Here comes the Chakra factory to the rescue, a very powerful tool!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;chakra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Box&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;@chakra-ui/react&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;MyBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chakra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;baseStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;papayawhip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red.500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// use the newly created component anywhere in your code&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyBox&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see in the example you can just pass existing Chakra components to the factory function and apply baseStyles.&lt;/p&gt;

&lt;p&gt;The cool thing is, you're not only able to adjust existing Chakra components, but also standard HTML elements like &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;span&lt;/code&gt;, etc. And if you're styling those elements with the Chakra factory, they also have the superpower to be adjusted with style props afterwards. This is really awesome 🎉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;chakra&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;@chakra-ui/react&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;MyDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chakra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&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;baseStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;papayawhip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red.500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// the newly created component now also has superpowers&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyDiv&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Styling nested elements
&lt;/h1&gt;

&lt;p&gt;Let's also cover a special usecase, where you have to style nested elements which aren't in you reach of being styled in the "Chakra way". As an example I use the &lt;code&gt;Image&lt;/code&gt; component provided by &lt;a href="https://nextjs.org"&gt;Next.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Consider you have the following way of styling the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/image&lt;/span&gt;&lt;span class="dl"&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="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"yourimage.png"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&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;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that's not the way we like it mh? We want to style it with Chakra, because e.g. you want to define different margins depending on the device size. Let's have a look the the &lt;code&gt;sx&lt;/code&gt; property of Chakra, another powerful tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Box&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;@chakra-ui/react&lt;/span&gt;&lt;span class="dl"&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="nc"&gt;Box&lt;/span&gt; &lt;span class="na"&gt;sx&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;img&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;40px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10px&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="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"yourimage.png"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;sx&lt;/code&gt; property you can basically style any nested element. Just apply the correct css selector. Even a selector like &lt;code&gt;div &amp;gt; img.className&lt;/code&gt; is possible here, just pass it as a string literal.&lt;/p&gt;

&lt;p&gt;It also helps with css properties which aren't yet supported natively by ChakraUI. Here you can see an example for &lt;a href="https://trueq.io/q/42#answer-42"&gt;styling scrollbars&lt;/a&gt; and &lt;a href="https://trueq.io/q/41#answer-41"&gt;specific WebKit properties&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Accessibility
&lt;/h1&gt;

&lt;p&gt;Another awesome thing to mention is, that Chakra comes with accessibility in mind. All the components which are provided to you by ChakraUI have accessibility baked in.&lt;/p&gt;

&lt;p&gt;As webapps should be as accessible as possible, this is an awesome prerequisite for starting to develop your webapp.&lt;/p&gt;

&lt;h1&gt;
  
  
  Framer Motion
&lt;/h1&gt;

&lt;p&gt;Since Version 1, Chakra comes with Framer Motion as default animation library. Chakra components are using Framer Motion for their animation and it is also recommended to use it for your custom animations throughout your app.&lt;/p&gt;

&lt;p&gt;For more information of Framer Motion checkout the &lt;a href="https://www.framer.com/api/motion"&gt;official docs&lt;/a&gt; or the &lt;a href="https://chakra-ui.com/guides/integrations/with-framer"&gt;integration example&lt;/a&gt; for Chakra. Framer Motion is an awesome animation library and I can just recommend to have a closer look at it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deeply integrated support for Dark Mode
&lt;/h1&gt;

&lt;p&gt;Dark mode is getting adopted more and more. Not only in the web but also in several operating systems. If you also want to support dark mode in your webapp you'll be happy to know that Chakra has a deeply integrated support for it.&lt;/p&gt;

&lt;p&gt;Chakra also takes care of saving the user's decision, either in the local storage or as a cookie which is helpful for server side rendering. It also can detect the users OS setting.&lt;/p&gt;

&lt;p&gt;There are several utilities to style or adjust your app depending on the current color mode.&lt;/p&gt;

&lt;h1&gt;
  
  
  Helpful Hooks
&lt;/h1&gt;

&lt;p&gt;Chakra also has some hooks which come in handy. For example I see myself using &lt;code&gt;useColorModeValue&lt;/code&gt; very often to provide a value depending on the current color mode.&lt;/p&gt;

&lt;p&gt;There are also hooks like &lt;code&gt;useClipboard&lt;/code&gt; and &lt;code&gt;useMediaQuery&lt;/code&gt; which could make some of the other dependencies in your project obsolete.&lt;/p&gt;

&lt;h1&gt;
  
  
  Awesome Community
&lt;/h1&gt;

&lt;p&gt;The community is a big part of a framework / library and I can say that the community around Chakra is just awesome. The people are friendly and want to help you. Checkout the &lt;a href="https://github.com/chakra-ui/chakra-ui/discussions"&gt;Chakra GitHub Discussions&lt;/a&gt; if you have any questions.&lt;/p&gt;

&lt;p&gt;I also want to mention that my Twitter friend &lt;a href="https://twitter.com/hauptrolle_"&gt;Achim&lt;/a&gt; created a new project called &lt;a href="https://chakra-templates.dev/"&gt;Chakra Templates&lt;/a&gt;. A growing collection of responsive Chakra UI Templates ready to drop into your project.&lt;/p&gt;

&lt;h1&gt;
  
  
  What if I don’t use React?
&lt;/h1&gt;

&lt;p&gt;At least I am happy to tell you that there's also an official &lt;a href="https://vue.chakra-ui.com/"&gt;Chakra version for Vue&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Hopefully you find this short overview about ChakraUI helpful. I wanted to show how it complements existing approaches in web development and extends them with nice paradigms and helpful utilities.&lt;/p&gt;

</description>
      <category>react</category>
      <category>chakra</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
