<?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: TRIGO | We speak human and computer</title>
    <description>The latest articles on DEV Community by TRIGO | We speak human and computer (@trigo).</description>
    <link>https://dev.to/trigo</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%2Forganization%2Fprofile_image%2F454%2F99e0df1e-f1de-42a6-94e9-bd692eaa47e4.png</url>
      <title>DEV Community: TRIGO | We speak human and computer</title>
      <link>https://dev.to/trigo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trigo"/>
    <language>en</language>
    <item>
      <title>How to customize Keycloak themes</title>
      <dc:creator>David Wippel</dc:creator>
      <pubDate>Thu, 21 Apr 2022 11:55:52 +0000</pubDate>
      <link>https://dev.to/trigo/how-to-customize-keycloak-themes-545</link>
      <guid>https://dev.to/trigo/how-to-customize-keycloak-themes-545</guid>
      <description>&lt;p&gt;In this insight, you will learn how Keycloak Themes are structured and how to come up with your own custom theme.&lt;/p&gt;

&lt;p&gt;Because we believe that learning is best done when putting everything into practices, we will start off by setting up a development environment. Right after, we will go over the types of themes and the folder structure. Closing with how to add customization and build your own Keycloak theme.&lt;/p&gt;

&lt;p&gt;Before we get started with all the details, a quick recap of what Keycloak is and why we care about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Keycloak?
&lt;/h2&gt;

&lt;p&gt;Keycloak is an open-source identity and access management or IAM solution, that can be used as a third-party authorization server to manage your web or mobile applications' authentication and authorization requirements.&lt;/p&gt;

&lt;p&gt;It lifts the heavy weight of implementing a stable and secure user authorization and authentication, from the shoulders of developers.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://trigodev.com"&gt;TRIGO&lt;/a&gt;, we use it for all of our projects. It saved tons of time and money. Kept us staying sane when integrating AuthN/AuthZ into an application. We love Keycloak.&lt;/p&gt;

&lt;p&gt;Now let's dive right into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a development environment
&lt;/h2&gt;

&lt;p&gt;You could either download Keycloak and run it natively on your machine, or spin up some container using docker or podman.&lt;/p&gt;

&lt;p&gt;We at &lt;a href="https://trigodev.com"&gt;TRIGO&lt;/a&gt; are heavily into containerization. Therefore, we will use the second option for this tutorial.&lt;/p&gt;

&lt;p&gt;To make things even simpler for you, we have prepared a fully functional setup as &lt;a href="https://github.com/trigo-at/keycloak-theme-development"&gt;Git repository&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before proceeding with this tutorial, please take your time to check out the repository and follow the instructions found in the README file. If you got any questions, please feel free to &lt;a href="https://github.com/trigo-at/keycloak-theme-development/issues/new"&gt;open an issue&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Theme types
&lt;/h2&gt;

&lt;p&gt;With a single theme, you can provide one or more types of customization.&lt;/p&gt;

&lt;p&gt;An overview:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Location of the demo repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Common&lt;/td&gt;
&lt;td&gt;Files (resources) shared along all theme types&lt;/td&gt;
&lt;td&gt;&lt;code&gt;themes/keycloak-provided-themes/keycloak/common&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account&lt;/td&gt;
&lt;td&gt;All things related to the Account management (self-service) area.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;themes/keycloak-provided-themes/base/account&lt;/code&gt; and &lt;code&gt;themes/keycloak-provided-themes/keycloak.v2/account&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin&lt;/td&gt;
&lt;td&gt;The admin console. Yes, the very extensive area can be customized too.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;themes/keycloak-provided-themes/base/admin&lt;/code&gt; and &lt;code&gt;themes/keycloak-provided-themes/keycloak.v2/admin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emails&lt;/td&gt;
&lt;td&gt;Everything you need to customize Emails sent by Keycloak&lt;/td&gt;
&lt;td&gt;&lt;code&gt;themes/keycloak-provided-themes/base/email&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login&lt;/td&gt;
&lt;td&gt;User-facing public pages like login, password reset and so on.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;themes/keycloak-provided-themes/base/login&lt;/code&gt; and &lt;code&gt;themes/keycloak-provided-themes/keycloak/login&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Welcome&lt;/td&gt;
&lt;td&gt;The welcome page shown when spinning up Keycloak and opening its landing page&lt;/td&gt;
&lt;td&gt;&lt;code&gt;themes/keycloak-provided-themes/keycloak/welcome&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt; Instructions on &lt;a href="https://www.keycloak.org/docs/latest/server_development/index.html#configuring-a-theme"&gt;how to configure a theme&lt;/a&gt; can be found in the Keycloak Documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure of a theme folder
&lt;/h2&gt;

&lt;p&gt;Every theme must consist of one or more type related subdirectories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7lli3uxp2xmb9uiy9lr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7lli3uxp2xmb9uiy9lr.png" alt="Image description" width="193" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a 1:1 reference to the theme types mentioned above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure of a theme type folder
&lt;/h2&gt;

&lt;p&gt;Every theme type folder must have at least a &lt;code&gt;theme.properties&lt;/code&gt; file. It contains basic instructions for Keycloak.&lt;/p&gt;

&lt;p&gt;Furthermore, it consists of one or more of the following folders&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Folder&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;messages&lt;/td&gt;
&lt;td&gt;Contains one or more messages_XX.properties files. They are part of the i18n mechanism of Keycloak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resources&lt;/td&gt;
&lt;td&gt;Every image, CSS or JavaScript file that is required to power the theme type is found here.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The email type is a little different. It contains an HTML and text folder containing the email templates for HTML and text emails. All as FTL files.&lt;/p&gt;

&lt;p&gt;Besides that, a theme type folder usually comes with many template files (.ftl file ending). They represent all the different pages Keycloak has.&lt;/p&gt;

&lt;h2&gt;
  
  
  All your base belongs to me. Theme Inheritance explained
&lt;/h2&gt;

&lt;p&gt;You might have noticed that for most types, there are multiple places where you can find related files. That's because Keycloak themes are built with inheritance in mind. Everything is (or it can be) based on &lt;code&gt;base&lt;/code&gt; (phun intended!). You could, theoretically, start from scratch, but we don't recommend that unless you have a special use case. It's just a lot of work to provide everything you need for a fully functional theme.&lt;/p&gt;

&lt;h3&gt;
  
  
  Theme type entry point: theme.properties
&lt;/h3&gt;

&lt;p&gt;Let's have a look at the entry point of the login type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4ao1r3k26fxftd2i04q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4ao1r3k26fxftd2i04q.png" alt="Image description" width="252" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is from themes/keycloak-provided-themes/keycloak/login&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first line of &lt;code&gt;theme.properties&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parent=base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, we instruct Keycloak to inherit our login type from the base theme. We can then focus on the things we like to change instead of starting from zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  FTL - Faster than light or Freemarker
&lt;/h2&gt;

&lt;p&gt;Freemarker is a template language like Pug, Mustache or EJS. If you are familiar with the primary template language of your ecosystem, you should be familiar with the core concepts of freemarker too.&lt;/p&gt;

&lt;p&gt;I still recommend going through the &lt;a href="https://freemarker.apache.org/docs/dgui_quickstart.html"&gt;“Template Author Guide”&lt;/a&gt;. The Keycloak Login Theme makes heavy use of the FTL inheritance features. So better be prepared to dig in deep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Models for your templates
&lt;/h2&gt;

&lt;p&gt;Keycloak uses numerous data models within their template files. They are - let just put it that way - not very well documented.&lt;/p&gt;

&lt;p&gt;To get at least a basic understanding, these two files from the Keycloak Source Code are a good starting point:&lt;/p&gt;

&lt;h3&gt;
  
  
  For Theme Type: Login
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  For Theme Type: Account
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What other tech is included?
&lt;/h2&gt;

&lt;p&gt;For the time of writing this insight, Keycloak 17 is the newest version available. There are announcements that the current angular1 based admin and account theme is replaced by a React-based solution anytime soon. Keycloak 15 already has an early version of that included. It can be found as &lt;code&gt;keycloak.v2&lt;/code&gt; theme here: &lt;code&gt;themes/keycloak-provided-themes/keycloak.v2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Login part of the theme uses an earlier version of the Patternfly Framework. Files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;themes/keycloak-provided-themes/keycloak/common/resources/web_modules/@patternfly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since there is no version information available, it's hard to tell which version of Patternfly it is, but all required resources are included.&lt;/p&gt;

&lt;p&gt;v4 of Patternfly is a &lt;a href="https://www.patternfly.org/v4/"&gt;remarkable framework found here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to customize a theme type?
&lt;/h2&gt;

&lt;p&gt;For all of you who, me included, are like: Ok Patternfly is cool and what not , but I want to bring my own stuff to the table. Tailwind, Vue, Svelte, Chakra, ... just a little more patience.&lt;/p&gt;

&lt;p&gt;Before we explore how to create a custom theme with your favorite tech stack, I think it's important to understand the structure of the default theme.&lt;/p&gt;

&lt;p&gt;For this tutorial, I will focus on the login theme type further on. I assume that that's the type most of you want to customize anyway. Also, when you understand that part of a theme, the e.g. account part is a piece of cake for you.&lt;/p&gt;

&lt;p&gt;The first file you should examine closer is &lt;code&gt;template.ftl&lt;/code&gt;. It contains the basic HTML page structure and all &amp;lt;#nested&amp;gt; instructions that are used in the various templates. It defines a macro that is used in pages like login, register, login-reset-password, …&lt;/p&gt;

&lt;p&gt;Speaking of the different templates. A quick excursion on what files are the more relevant ones:&lt;/p&gt;

&lt;h3&gt;
  
  
  Login theme type most relevant templates
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Folder&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;login.ftl&lt;/td&gt;
&lt;td&gt;Username/Password login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;register.ftl&lt;/td&gt;
&lt;td&gt;User registration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;login-reset-password.ftl&lt;/td&gt;
&lt;td&gt;Password Reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;login-update-password.ftl&lt;/td&gt;
&lt;td&gt;Password Update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;login-otp.ftl&lt;/td&gt;
&lt;td&gt;OTP/2FA login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;error.ftl&lt;/td&gt;
&lt;td&gt;Error page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Back to customizing the login theme type
&lt;/h3&gt;

&lt;p&gt;Within the FTL templates various data-models are used as mentioned above. It's beyond the scope of this tutorial to explain every detail. I strongly recommend to set aside some time to go through ALL the files and explore how the various properties are used and for what purpose.&lt;/p&gt;

&lt;p&gt;If you got any questions, please feel free to drop me an email at &lt;a href="mailto:david@trigodev.com"&gt;david@trigodev.com&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS Classes confusion
&lt;/h3&gt;

&lt;p&gt;Said that, there is one detail I like to explain a bit. How CSS classes are handled. It was a source of confusion for me when starting with Keycloak theme development. CSS Classes are set via properties that are defined in the &lt;code&gt;theme.properties&lt;/code&gt; file of the login theme type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ns58ppnvsnqy4c2rwug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ns58ppnvsnqy4c2rwug.png" alt="Image description" width="528" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't get the reason behind that decision. I guess the idea was to provide a way to customize the CSS classes in a central way. It was one of the first things I dropped when building the first themes for ourselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Included Messages for i18n
&lt;/h3&gt;

&lt;p&gt;Let's assume you have added some new i18n message to one of the message_xx.properties files in the messages' folder. For example, some marketing information you want to include on the login page.&lt;/p&gt;

&lt;p&gt;Freemarker provides a &lt;code&gt;msg&lt;/code&gt; function to include i18n messages.&lt;/p&gt;

&lt;p&gt;Note: It will refer to the language currently selected by the user. Keycloak uses Browser information to determine the language, but only if the language is configured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;${msg("doForgotPassword")}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Some notes on security
&lt;/h2&gt;

&lt;p&gt;All things you know about web security are relevant for Keycloak themes too. Keep in mind that you are basically building a website (or web application for the account/admin theme type).&lt;/p&gt;

&lt;p&gt;Therefore, just a few short notes on security:&lt;/p&gt;

&lt;h3&gt;
  
  
  Sanitizing all user-generated content
&lt;/h3&gt;

&lt;p&gt;Keycloak provides a &lt;code&gt;kcSanitize&lt;/code&gt; function to sanitize HTML content. I highly recommend using that function when including user provided HTML content in any way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;${kcSanitize(message.summary)?no_esc}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/theme/KeycloakSanitizerMethod.java"&gt;Code found here.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CSRF Protection
&lt;/h3&gt;

&lt;p&gt;According to the OAUTH2 Specification, CSRF Protection is done via a state query parameter. Keycloak handles that for us via the &lt;code&gt;url.loginAction&lt;/code&gt; property used as form action&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't change that unless you know exactly what you are doing here.&lt;/p&gt;

&lt;p&gt;A more detailed explanation on how the state parameter helps to prevent CSRF attacks &lt;a href="https://auth0.com/docs/secure/attack-protection/state-parameters"&gt;can be found here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring your own stuff
&lt;/h2&gt;

&lt;p&gt;As you might have guessed, besides learning all the details about the tech stack involved and some innards about the data-model used, there is nothing special to a Keycloak theme. It's just web development. So, why not bring your well-known framework of choice to the table? The good news is that, there is no reason not to go down that road.&lt;/p&gt;

&lt;p&gt;In fact, we do that by using Tailwind to style our themes.&lt;/p&gt;

&lt;p&gt;I've included an example to the Git Repository. You are welcome to use that as a starting point (see themes folder).&lt;/p&gt;

&lt;p&gt;Note: The themes are mapped into the &lt;code&gt;docker-compose.yml&lt;/code&gt; to make them visible to Keycloak. You have to add your own theme to that by duplicating and adopt this line in the &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./themes/tailwind-example/:/opt/jboss/keycloak/themes/tailwind-example/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;We have learned how Keycloak themes are structured, what files are important, how inheritance works for themes and how to get started with custom themes.&lt;/p&gt;

&lt;p&gt;I've tried to add as many lessons learned as possible. If anything isn't super clear or missing, please point that out.&lt;/p&gt;

&lt;p&gt;Again, if you got any questions, please reach out to me.&lt;/p&gt;

</description>
      <category>keycloak</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to test a Hasura Api with Jest</title>
      <dc:creator>Thomas Peklak</dc:creator>
      <pubDate>Fri, 27 Aug 2021 06:51:18 +0000</pubDate>
      <link>https://dev.to/trigo/how-to-test-a-hasura-api-with-jest-36jh</link>
      <guid>https://dev.to/trigo/how-to-test-a-hasura-api-with-jest-36jh</guid>
      <description>&lt;p&gt;Hasura provides an easy way to build an API and testing it is as easy as testing any other API. We will focus on the GraphQL API but everything presented here can be applied to the REST API as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case
&lt;/h2&gt;

&lt;p&gt;We will model a todo list for different users. Each user should only see her own todo items and none of a different user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;We will use&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Docker:&lt;/strong&gt; to setup Hasura and PostgreSQL&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Jest:&lt;/strong&gt; as our test runner&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;graphql-codegen:&lt;/strong&gt; to generate an SDK including Typescript support to have good autocompletion in
the editor&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;JWT:&lt;/strong&gt; to set the authenticated user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... to test that the business logic in Hasura works as intended. Business logic can live in Hasura itself (e.g. permissions) or within the database (e.g. views, constraints, et al.). Note that it does not make sense to test that Hasura does its job correctly as it is by itself well tested. If you just create a resource without any logic you probably do not want to test it.&lt;/p&gt;

&lt;p&gt;If you want to follow along you can clone the sample repository at &lt;a href="https://github.com/trigo-at/hasura-api-testing"&gt;trigo/hasura-api-testing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hasura and PostgreSQL Setup with Docker Compose
&lt;/h2&gt;

&lt;p&gt;We use a slightly modified version of Hasura's &lt;a href="https://hasura.io/docs/latest/graphql/core/getting-started/docker-simple.html#step-1-get-the-docker-compose-file"&gt;docker compose file&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Changes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;HASURA_GRAPHQL_JWT_SECRET&lt;/code&gt; for user authentication&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;cli-migrations&lt;/code&gt; image so that migrations are automatically applied&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;graphql-engine/volumes&lt;/code&gt; to be able to store migrations and metadata in the repository&lt;/li&gt;
&lt;li&gt;  exposed &lt;code&gt;port&lt;/code&gt; for PostgreSQL to be able to connect directly to PostgreSQL
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;// docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.6"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:12&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
  &lt;span class="na"&gt;graphql-engine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hasura/graphql-engine:v2.0.3.cli-migrations-v3&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${PWD}/migrations:/hasura-migrations&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${PWD}/metadata:/hasura-metadata&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_METADATA_DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://postgres:password@postgres:5432/postgres&lt;/span&gt;
      &lt;span class="na"&gt;PG_DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://postgres:password@postgres:5432/postgres&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_ENABLE_CONSOLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_DEV_MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_ENABLED_LOG_TYPES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;startup, http-log, webhook-log, websocket-log, query-log&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_ADMIN_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin-secret&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_JWT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"type":"HS256",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"key":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3EK6FD+o0+c7tzBNVfjpMkNDi2yARAAKzQlk8O2IKoxQu4nF7EdAh8s3TwpHwrdWT6R",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"claims_map":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"x-hasura-allowed-roles":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"path":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$$.roles"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;},&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"x-hasura-default-role":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"path":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$$.roles[0]"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;},&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"x-hasura-client-id":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"path":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$$.clientId",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"default":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;},&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"x-hasura-user-id":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"path":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$$.userId",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"default":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;},&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"x-hasura-username":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"path":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$$.username",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"default":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start Hasura and PostgreSQL with &lt;code&gt;docker-compose up -d&lt;/code&gt; and wait until Hasura is up and running. You can either check via &lt;code&gt;curl localhost:8080/healthz&lt;/code&gt; or &lt;code&gt;hasura migrate status --admin-secret admin-secret&lt;/code&gt; to see whether all migrations have been applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Database and Permissions
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;When you spin up the compose file within the provided repository you can skip the next steps because migrations and metadata are included and applied automatically when Hasura starts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next we create a &lt;code&gt;todo&lt;/code&gt; table with properties &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;is_done&lt;/code&gt;, and &lt;code&gt;user&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"todo"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"description"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"is_done"&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"user"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Insert permissions:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When inserting new todos, we do not allow the user to set the &lt;code&gt;user&lt;/code&gt; property by herself, instead we use the &lt;code&gt;user-id&lt;/code&gt; session variable for the user and only allow the properties &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;is_done&lt;/code&gt; to be set. &lt;code&gt;id&lt;/code&gt; is an autoincrement field and should not be touched by the user either.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nkkDjRXQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlznaiynz27h4m8z77w1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nkkDjRXQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlznaiynz27h4m8z77w1.png" alt="insert permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Select permissions:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user retrieves her todos, we set the custom check &lt;code&gt;user&lt;/code&gt; equals &lt;code&gt;x-hasura-user-id&lt;/code&gt; to ensure that a user only gets her own todos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lXXAvHZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nfus8z5tlm395w813ah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lXXAvHZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nfus8z5tlm395w813ah.png" alt="select permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have now completed our PostgreSQL and Hasura setup and are ready to look into the test setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Code Generation
&lt;/h3&gt;

&lt;p&gt;We leverage &lt;code&gt;@graphql-codegen&lt;/code&gt; to generate an SDK for the GraphQL API. It will take all &lt;code&gt;.graphql&lt;/code&gt; files under &lt;code&gt;tests&lt;/code&gt;, connect to the Hasura GraphQL API and output the SDK in &lt;code&gt;tests/client/graphql.request.ts&lt;/code&gt;. This takes over most of the communication between our tests and the GraphQL API and gives us typing information to help us in the editor and in our tests.&lt;/p&gt;

&lt;p&gt;You can find detailed information at &lt;a href="https://www.graphql-code-generator.com/"&gt;GraphQL Code Generator&lt;/a&gt; on the possibilities to generate code from your GraphQL schema.&lt;/p&gt;

&lt;p&gt;First we need a &lt;code&gt;.graphql&lt;/code&gt; file that describes how to insert and select todos. It has a &lt;em&gt;query&lt;/em&gt; &lt;code&gt;GetTodos&lt;/code&gt; that retrieves&lt;br&gt;
all todos and a &lt;em&gt;mutation&lt;/em&gt; &lt;code&gt;AddTodo&lt;/code&gt; which inserts a new todo item.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/graphql/todo.graphql
query GetTodos {
  todo {
    id
    description
    is_done
  }
}

mutation AddTodo($description: String = "", $is_done: Boolean = false) {
  insert_todo(objects: { description: $description, is_done: $is_done }) {
    returning {
      id
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;graphql-codegen/cli&lt;/code&gt; needs a configuration file so it knows where our GraphQL API lives, where to look for source files and where to put the generated SDK. You can also specify plugins which modify the output of SDK. More information can be found in the &lt;a href="https://www.graphql-code-generator.com/docs/plugins/index"&gt;graphql-codegen plugin index&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We will use&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;typescript:&lt;/strong&gt; Generate types for TypeScript - those are usually relevant for both client side and server side code&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;typescript-operations:&lt;/strong&gt; Generate client specific TypeScript types (query, mutation, subscription, fragment)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;typescript-graphql-request:&lt;/strong&gt; Generates fully-typed ready-to-use SDK for graphql-request
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// codegen.js&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="na"&gt;schema&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8080/v1/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-hasura-admin-secret&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;admin-secret&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;span class="na"&gt;documents&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;./tests/**/*.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;overwrite&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;generates&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;./tests/utils/graphql.request.ts&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;plugins&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;typescript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typescript-operations&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;typescript-graphql-request&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;p&gt;Now install all necessary npm modules&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-graphql-request @graphql-codegen/typescript-operations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and generate the SDK with &lt;code&gt;npx graphql-codegen --config codegen.js&lt;/code&gt;. This should generate an SDK under &lt;code&gt;tests/utils/graphql.request.ts&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authenticated Requests
&lt;/h3&gt;

&lt;p&gt;The SDK itself does not have an easy way to create authenticated requests with JWT, you need to generate and specify the headers for each request. But we can simply wrap it for our purposes so that authentication headers are automatically sent with each request. The configuration will be sourced from the &lt;code&gt;.env&lt;/code&gt; file which should include Hasura and PostgreSQL configuration variables, see &lt;a href="https://github.com/trigo-at/hasura-api-testing/blob/main/.env"&gt;.env example&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/utils/client.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;GraphQLClient&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;graphql-request&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;getSdk&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;./graphql.request&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="nx"&gt;jwt&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;jsonwebtoken&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="nx"&gt;dotenv&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;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// source environment variables&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have set up the imports and read in the configuration variables. We can now extract the key from the Hasura configuration parameter (see &lt;code&gt;docker-compose.yml&lt;/code&gt; above).&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;let&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&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="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HASURA_GRAPHQL_JWT_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;key&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HASURA_GRAPHQL_JWT_SECRET must be parsable json and have property key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client takes options for either a user or an admin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;User:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;allowedRoles:&lt;/em&gt; all roles that a user can have, this will be sent within the JWT&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;defaultRole:&lt;/em&gt; the role that is currently active, is sent in the &lt;code&gt;x-hasura-role&lt;/code&gt; header, but must
also be present in the JWT&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;userId:&lt;/em&gt; the user id&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;username:&lt;/em&gt; the username&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Admin:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;admin:&lt;/em&gt; boolean used to make requests with the &lt;code&gt;admin-secret&lt;/code&gt;. No other option needs to be present
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;allowedRoles&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="nl"&gt;defaultRole&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="nl"&gt;userId&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="nl"&gt;username&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AdminOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;admin&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="c1"&gt;// define the configuration options for the client&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserOptions&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;AdminOptions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we have a user we map the &lt;em&gt;default role&lt;/em&gt; to the &lt;code&gt;x-hasura-role&lt;/code&gt; header which corresponds to the role in the Hasura Console permissions tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4YsN5aju--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ymsy7vvtzi9je3gubng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4YsN5aju--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ymsy7vvtzi9je3gubng.png" alt="roles"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mapOptionsToHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Options&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-hasura-role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultRole&lt;/span&gt; &lt;span class="o"&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;All other user options are packed into the JWT which will be read by Hasura and provided as variables for permissions.&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;generateJwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowedRoles&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
            &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&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="nx"&gt;secret&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now create the main entry point which takes the options and returns an SDK with a user specific GraphQL client embedded.&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;default&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;getSdk&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GRAPHQL_ENDPOINT&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="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRAPHQL_ENDPOINT is not defined&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;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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HASURA_GRAPHQL_ADMIN_SECRET&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="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HASURA_GRAPHQL_ADMIN_SECRET is not defined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// if we do not provide allowedRoles for the client we assume that the defaultRole is an allowed role&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;defaultRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowedRoles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowedRoles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultRole&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We configure a GraphQL client with either an admin user (with an admin secret) or a normal user (with JWT) and pass it to the SDK factory function. This will ensure that all request have the correct headers.&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;GraphQLClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GRAPHQL_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-hasura-admin-secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HASURA_GRAPHQL_ADMIN_SECRET&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;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;generateJwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&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="nx"&gt;mapOptionsToHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&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;getSdk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Todo Permissions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recap:&lt;/strong&gt; We want to ensure that users see only their respective to todos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;clear the database before each test&lt;/li&gt;
&lt;li&gt;create 2 todos for user &lt;em&gt;Franz&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;create 1 todo for user &lt;em&gt;Herbert&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;get todos for user &lt;em&gt;Franz&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;get todos for user &lt;em&gt;Herbert&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;test that each user sees only his todos&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Import dependencies&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="nx"&gt;clearDb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;closeConnection&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/clear-db&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="nx"&gt;client&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/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup the test and hooks&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Todo&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// reset the database before each run&lt;/span&gt;
  &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clearDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// close PG connection after all tests&lt;/span&gt;
  &lt;span class="nx"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;closeConnection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create an SDK client for each user. We provide the &lt;code&gt;defaultRole&lt;/code&gt; (user), a &lt;code&gt;username&lt;/code&gt; and a &lt;code&gt;userId&lt;/code&gt; all client requests are scoped to the passed in options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user: Franz + Herbert&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should only see his todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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;franzClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;defaultRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;franz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;userId&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&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;herbertClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;defaultRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;herbert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert two todos into the database for user &lt;em&gt;Franz&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;franzTodo1&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;franzClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Franz's first todo item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;is_done&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;franzTodo2&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;franzClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Franz's second todo item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;is_done&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert one todo for user Herbert&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;herbertTodo1&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;herbertClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Herbert's first todo item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;is_done&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have &lt;strong&gt;three&lt;/strong&gt; todos in the database two for &lt;em&gt;Franz&lt;/em&gt; and one for &lt;em&gt;Herbert&lt;/em&gt;. We retrieve the todos for each user and assert that the user only sees his own todo items.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="c1"&gt;// get Franz's todos&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;franzsTodos&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;franzClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// Franz should only see his todos&lt;/span&gt;
      &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;franzsTodos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&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="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
          &lt;span class="nx"&gt;franzTodo1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert_todo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;returning&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;franzTodo2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert_todo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;returning&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;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

      &lt;span class="c1"&gt;// get Herbert's todos&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;herbertsTodos&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;herbertClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// Herbert should only see his todos&lt;/span&gt;
      &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;herbertsTodos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&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="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;herbertTodo1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert_todo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;returning&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;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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It is really easy to setup a Hasura API but with this ease we often forget that business logic should be well tested. As soon as the test setup is done it is rather easy to write tests against the API. TypeScript and the tests help to see any breaking API changes that would let your application fail in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

&lt;p&gt;Due to the current setup it is not possible to run tests in parallel. The API tests provide no means to isolate a test in a transaction. Keep this in mind as it will increase the time of your test runs.&lt;/p&gt;

&lt;p&gt;This is all just a starting point.&lt;/p&gt;

</description>
      <category>hasura</category>
      <category>graphql</category>
      <category>testing</category>
      <category>jest</category>
    </item>
    <item>
      <title>Develop Electron in Docker</title>
      <dc:creator>Thomas Peklak</dc:creator>
      <pubDate>Wed, 25 Aug 2021 14:30:10 +0000</pubDate>
      <link>https://dev.to/trigo/develop-electron-in-docker-52h3</link>
      <guid>https://dev.to/trigo/develop-electron-in-docker-52h3</guid>
      <description>&lt;h2&gt;
  
  
  Why
&lt;/h2&gt;

&lt;p&gt;There are several reasons to develop in Docker and not on your host machine. First, when it comes to backend services, you can guarantee that your development runtime is the same as in production, which can eliminate some scenarios where inconsistencies can lead to problems in production.&lt;/p&gt;

&lt;p&gt;Of course, this doesn’t really apply to Electron because their apps are bundled up and shipped to your users — here you don’t have any control over the environment. Even so, you can have build dependencies where you want to have either an easier setup for the whole development team or you want to guarantee that every developer has the same system dependencies as your build servers installed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My case was different:&lt;/strong&gt; my colleague had to introduce a new dependency with a native module. It worked well on his machine, but created a segfault on mine. I’m not familiar with debugging segfaults and getting my development setup up and running again was urgent. Before I turned to Docker I tried different versions of Electron and the library but always ended up with the same result: it worked on other machines but not on mine. The library even worked well using the corresponding NodeJS version there only seemed to be a problem with Electron.&lt;/p&gt;

&lt;p&gt;As I thought that this was just a temporary issue and would be fixed in future versions of the library or Electron, I was happy to have a temporary solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  How
&lt;/h2&gt;

&lt;p&gt;Based on this directory structure we can build a Docker image from a Dockerfile which installs all node modules, performs an electron rebuild, and does some other black magic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Directory Structure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  package.json&lt;/li&gt;
&lt;li&gt;  package-lock.json&lt;/li&gt;
&lt;li&gt;  Dockerfile&lt;/li&gt;
&lt;li&gt;  src

&lt;ul&gt;
&lt;li&gt;  main.js&lt;/li&gt;
&lt;li&gt;  index.html&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s important that your files are not located in the root folder because we want to mount the source directory into the container, so we can continuously work without rebuilding the image. We only need to rebuild the image if there is a change in the npm dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# use the version that corresponds to your electron version&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:14.16&lt;/span&gt;

&lt;span class="c"&gt;# install electron dependencies or more if your library has other dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git libx11-xcb1 libxcb-dri3-0 libxtst6 libnss3 libatk-bridge2.0-0 libgtk-3-0 libxss1 libasound2 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-yq&lt;/span&gt; &lt;span class="nt"&gt;--no-install-suggests&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# copy the source into /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; node /app

&lt;span class="c"&gt;# install node modules and perform an electron rebuild&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npx electron-rebuild

&lt;span class="c"&gt;# Electron needs root for sand boxing&lt;/span&gt;
&lt;span class="c"&gt;# see https://github.com/electron/electron/issues/17972&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;root /app/node_modules/electron/dist/chrome-sandbox
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;4755 /app/node_modules/electron/dist/chrome-sandbox

&lt;span class="c"&gt;# Electron doesn't like to run as root&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can build the Docker image and start a container. If you have other files and folders in your root directory, you need to mount each single one separately. You can’t mount the root directory as this will also overwrite the node modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; electron-wrapper &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /tmp/.X11-unix:/tmp/.X11-unix &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DISPLAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unix&lt;span class="nv"&gt;$DISPLAY&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/src:/app/src &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; electron-wrapper bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should fire up a bash prompt from which you can start your development workflow, e.g. &lt;code&gt;npm start&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example &lt;code&gt;package.json&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electron-in-docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electron . --no-sandbox"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"electron"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^13.1.7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"electron-rebuild"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.3.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"libxmljs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.19.7"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This guide works in Linux and likely in WSL2. For macOS, you can take a look at Jake Donham's blog post in the links below, which helped me a lot to get started. I always find it good to have some backup in my developer's toolbox if things break in unpredictable ways. Running programs in Docker is one of my all-time favorites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://blog.jessfraz.com/post/docker-containers-on-the-desktop/"&gt;https://blog.jessfraz.com/post/docker-containers-on-the-desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://jaked.org/blog/2021-02-18-How-to-run-Electron-on-Linux-on-Docker-on-Mac"&gt;https://jaked.org/blog/2021-02-18-How-to-run-Electron-on-Linux-on-Docker-on-Mac&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example repository
&lt;/h2&gt;

&lt;p&gt;If you want to try it out yourself, you can take a look at the &lt;a href="https://github.com/trigo-at/electron-in-docker"&gt;electron in docker repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>electron</category>
    </item>
  </channel>
</rss>
