<?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: Upside</title>
    <description>The latest articles on DEV Community by Upside (@upsidelab).</description>
    <link>https://dev.to/upsidelab</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%2F11107%2F6dc64274-000e-4dcb-9f6e-4d8181a0e5da.jpeg</url>
      <title>DEV Community: Upside</title>
      <link>https://dev.to/upsidelab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/upsidelab"/>
    <language>en</language>
    <item>
      <title>🌍 Scaling Globally with Spree? Here's How to Automate Translations (Using DeepL + 1 Line of Code)</title>
      <dc:creator>Chris Yaowen Zhang</dc:creator>
      <pubDate>Wed, 23 Jul 2025 13:11:47 +0000</pubDate>
      <link>https://dev.to/upsidelab/scaling-globally-with-spree-heres-how-to-automate-translations-using-deepl-1-line-of-code-3476</link>
      <guid>https://dev.to/upsidelab/scaling-globally-with-spree-heres-how-to-automate-translations-using-deepl-1-line-of-code-3476</guid>
      <description>&lt;p&gt;Manually translating product catalogs? Not only is it tedious — it slows down your entire international expansion.&lt;/p&gt;

&lt;p&gt;The good news? With Spree 4.6 and a new gem called spree_automation_interfaces, you can plug in translation tools like DeepL and have your products auto-translated with the click of a button. &lt;/p&gt;

&lt;p&gt;Let me show you how we got it working in under 20 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;🛒 Problem: Spree supports translations, but the process is still manual&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Spree’s backend already allows you to manage product translations across multiple locales. But historically, companies relied on exporting CSVs → emailing translators → re-importing the files — a messy, slow, error-prone loop.&lt;/p&gt;

&lt;p&gt;Even with tools like Google Translate or DeepL, manual copy-paste just doesn't scale.&lt;/p&gt;

&lt;p&gt;So we decided to automate the process, while still keeping room for human review.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;🔧 Solution: Spree Automation Interfaces + DeepL&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As a part of supporting Spree users, we at &lt;a href="https://upsidelab.io/" rel="noopener noreferrer"&gt;Upside&lt;/a&gt; published spree_automation_interfaces that lets you connect automated translation services directly to your Spree store.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll show you how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up your store for multiple languages (French &amp;amp; German)&lt;/li&gt;
&lt;li&gt;Plug in DeepL using a custom provider &lt;/li&gt;
&lt;li&gt;Add an “Auto-Translate” button inside your admin panel&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Enable Multiple Languages in Spree&lt;/strong&gt;&lt;br&gt;
(You can skip this if your store already supports multiple locales)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Spree Admin Panel, go to Settings → Store in the sidebar to the left.&lt;/li&gt;
&lt;li&gt;You will see a list of "Supported Locales". For the purpose of this guide, we'll add set up additional locales: French and German.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Click Update.&lt;/li&gt;
&lt;li&gt;To confirm that the setup works correctly, navigate to a product in the admin panel, and go to its "Translations" tab. You should see a table for filling additional translations - this means that the setup is correct.&lt;/li&gt;
&lt;/ol&gt;

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

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

&lt;p&gt;🎉 At this point, you can provide the translations manually. It's time to automate the process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Add the Right Gems&lt;/strong&gt;&lt;br&gt;
We need to add the following gems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spree_automation_interfaces - that brings the capability of automated translations into Spree&lt;/li&gt;
&lt;li&gt;deface - to allow spree_automation_interfaces to add new features to the admin panel&lt;/li&gt;
&lt;li&gt;deepl-rb - which we will use as a translations provider
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'spree_automation_interfaces', github: 'spree-contrib/spree_automation_interfaces'
gem 'deface'
gem 'deepl-rb', require: 'deepl'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And restart your development server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Implement the DeepL Translations Provider&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, we need to provide an implementation for the Automation Interfaces, that will be called by Spree when the user requests translations to be created.&lt;/p&gt;

&lt;p&gt;The required interface is defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module Spree
  module Products
    module Translations
      interface _AutomatedTranslationsProvider
        def call: (source_attributes: Hash[String, String], source_locale: String, target_locale: String) -&amp;gt; Hash[Symbol | String, String]
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create an implementation in app/services/spree/translations/deepl_translations_provider.rb&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module Spree
  module Translations
    class DeeplTranslationsProvider
      def call(source_attributes:, source_locale:, target_locale:)
        name = source_attributes['name']
        description = source_attributes['description']
        translated_name, translated_description = DeepL.translate([name, description], nil, target_locale)
        {
          'name' =&amp;gt; translated_name,
          'description' =&amp;gt; translated_description
        }
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Initialize DeepL Client&lt;/strong&gt;&lt;br&gt;
We also need to initialize DeepL's client. Let's create the following initializer config/initalizers/deepl.rb&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DeepL.configure do |config|
  config.host = ENV.fetch('DEEPL_HOST', 'https://api-free.deepl.com')
  config.auth_key = ENV.fetch('DEEPL_API_KEY')
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Plug Your Provider into Spree&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finally, configure Spree to use the provider we've just created&lt;/p&gt;

&lt;p&gt;In config/initializers/spree_automation_interfaces.rb&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rails.application.config.after_initialize do  
  SpreeAutomationInterfaces::Dependencies.products_automated_translations_provider = 'Spree::Translations::DeeplTranslationsProvider'
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Try It!&lt;/strong&gt;&lt;br&gt;
Head to any product’s Translations tab in the admin panel.&lt;/p&gt;

&lt;p&gt;You’ll see a new button: “Translate automatically”&lt;/p&gt;

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

&lt;p&gt;Once you click it, it will fetch translations using the provider we've just implemented and add them to the product.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;strong&gt;Bonus: Go Further with Sidekiq or API Triggers&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This interface also works programmatically, so you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger translations from code&lt;/li&gt;
&lt;li&gt;Run a background job to translate your entire catalog&lt;/li&gt;
&lt;li&gt;Schedule daily translation syncs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you could build a Sidekiq worker that batches and translates 10,000 SKUs overnight.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>automation</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Improving INP: How to Make Your React App Feel Faster</title>
      <dc:creator>Chris Yaowen Zhang</dc:creator>
      <pubDate>Wed, 09 Jul 2025 12:52:42 +0000</pubDate>
      <link>https://dev.to/upsidelab/improving-inp-how-to-make-your-react-app-feel-faster-2kaa</link>
      <guid>https://dev.to/upsidelab/improving-inp-how-to-make-your-react-app-feel-faster-2kaa</guid>
      <description>&lt;p&gt;If your React app feels a little sluggish when users click, type, or scroll—it’s not just a UX issue anymore. Google’s new INP (Interaction to Next Paint) metric measures how quickly your app responds to interactions, and it now plays a role in SEO rankings.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is the “Interaction to Next Paint” (INP) metric?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Google released a new Core Web Vital called Interaction to Next Paint (INP) in 2024. It measures the speed of interactions with the website. &lt;/p&gt;

&lt;p&gt;In this article, we will walk you through INP from the developer’s perspective. This includes guiding you through testing for INP, verifying potential causes of low INP scores, and how you can leverage new React tools to optimize your website for recent changes.&lt;/p&gt;

&lt;p&gt;Interaction to Next Paint (INP) is a scoring mechanism that analyzes latency from the time the user begins the interaction to the moment the next frame is presented with visual feedback. We’ll take a closer look at what mechanisms in modern applications cause low INP score later.&lt;/p&gt;

&lt;p&gt;It measures for one of the following interactions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clicking with a mouse&lt;/li&gt;
&lt;li&gt;Tapping on a device with a touchscreen&lt;/li&gt;
&lt;li&gt;Pressing a key on either a physical or onscreen keyboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, when the user types a character into the search input that filters a list of cats by their breeds, the INP value will be a duration from the moment of triggering a group of keyboard events (keydown, …, keyup) to the moment of re-rendering the updated list&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/2o6K1d0-A5I"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The main goal of INP is to minimize this duration, ensuring swift visual feedback for user interactions and optimizing overall responsiveness on the page.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What INP isn't?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There is already a well-known metric called “First Input Delay” (FID) which measures the time from when a user &lt;strong&gt;first&lt;/strong&gt; interacts with a page to the time when the main thread is next idle. It offered developers a new way to measure responsiveness as real users experience it.&lt;/p&gt;

&lt;p&gt;Read more: &lt;a href="https://web.dev/articles/fid" rel="noopener noreferrer"&gt;https://web.dev/articles/fid&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;FID vs INP - What’s the difference?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Comparing these two mechanisms reveals similar measurement goal - a page responsiveness. One of the essential differences is that the FID measures only initial interaction, while the INP takes the worst latency recorded during the entire session.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 There's a little nuance to how the INP is calculated depending on the total interactions per website. For websites with more than 50 interactions per session, it takes the 75th percentile of the slowest latency recorded. This eliminates potential hiccups for pages with large numbers of interactions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even though first impressions are important, the first interaction is not necessarily representative of all interactions throughout the life of a page. This is one of the reasons why INP replaces the FID.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Should you care about INP?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In short, yes, if you care about SEO or user experience. The INP will be another metric that not only directly influences your website's ranking on Google search results but also provides valuable insights for tracking the responsiveness of your product.&lt;/p&gt;

&lt;p&gt;Receiving poor results from the INP might be a sign to profile your application for performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Measuring INP&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For a released product, that runs in a production environment, accumulates user traffic, and qualifies for inclusion in the Chrome User Experience Report (CrUX) you can quickly measure the new INP metric using &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed insights&lt;/a&gt;. It gives a snapshot of your page and origin's performance over the previous 28 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Manually diagnose slow interactions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Ideally, you should rely on the field data from the CrUX report, that suggests your website struggles with slow interactions. Sometimes, however, it’s not possible, and we need another way.&lt;/p&gt;

&lt;p&gt;The main goal is to identify the problem with lowest amount of effort at first. For manual investigation, we’ll use the &lt;a href="https://chromewebstore.google.com/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?pli=1" rel="noopener noreferrer"&gt;Web Vitals addon&lt;/a&gt;. Then, dive deeper into what causes the issue. For that, we can leverage the Profiler available in the browser’s DevTools tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Web Vitals addon (Chrome)&lt;/strong&gt;&lt;br&gt;
Requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.google.com/chrome/" rel="noopener noreferrer"&gt;Chrome browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?pli=1" rel="noopener noreferrer"&gt;Web Vitals&lt;/a&gt; addon&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi" rel="noopener noreferrer"&gt;React Developer Tools&lt;/a&gt; addon (Useful for highlighting component updates)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with a simple configuration of the Web Vitals addon. This will display HUD overlay as well as log information about each interaction with the page in the console.&lt;/p&gt;

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

&lt;p&gt;We’ll use React Developer Tools to highlight component updates. This will help us with identifying what’s happening in our application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Once React Developer Tools is installed, open Chrome developer tools, then “Profiler” section. Enable “Highlight updates when components render”.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Let’s go back to previously introduced app which allows us to filter cats by their breeds. First issue we can observe is that the list itself is long. We’re displaying over 1900 results at once. That’s already something we could optimize, but let’s leave it for later. Let’s try to interact with the page by typing “B” into the input, and then clearing it.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/U21oQiJ75-8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Few things are happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A controlled Input component updates immediately on each character we type.&lt;/li&gt;
&lt;li&gt;A list updates on each input change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The moment of “freeze” shown on the video is the main issue that causes INP score to suffer. We can verify that by observing the INP delta times logged in the console on the right. This translates into general poor result of the INP score for our website (344 ms).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What exactly causes poor INP results?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Figuring out what's causing poor INP is the most important, and the most difficult step on the road of enhancing the User Experience (UX).&lt;/p&gt;

&lt;p&gt;As Google Chrome’s team suggest, interactions can be broken down into three phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The input delay: It starts when the user initiates an interaction with the page, and ends when the event callbacks for the interaction begin to run.&lt;/li&gt;
&lt;li&gt;The processing time: It consist of the time it makes for event callbacks to run to completion.&lt;/li&gt;
&lt;li&gt;The presentation delay: It’s the time it takes for the browser to present the next frame which contains the visual result of the interaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;I found a bottleneck - now what? Optimizing your React app for performance.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As a React developer you’re probably aware of core concepts used to optimize the app.&lt;/p&gt;

&lt;p&gt;I’d distinguish following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memoization memo, useMemo, useCallback&lt;/li&gt;
&lt;li&gt;Debouncing/Throttling (using a hook or a higher-order-function that triggers the callback)&lt;/li&gt;
&lt;li&gt;List virtualization&lt;/li&gt;
&lt;li&gt;List pagination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s go back to our simple app about cats. Take a look at the component that provides browsing functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function CatsBreedsBrowserTemplate() {
  // 1. Fetching a list of all cats from the API (no limitting the response).
  const { data, isLoading } = useCatsData();

  // 2. Storing a `SearchBar` value.
  const [filterByBreed, setFilterByBreed] = useState("");

  // 3. Filtering the `data` array based on the `filterByBreed`.
  const searchResults = useMemo(() =&amp;gt; {
    const catsData = data ?? [];

    if (!filterByBreed || !catsData.length) {
      return catsData;
    }

    return catsData.filter((cat) =&amp;gt; cat.breed.includes(filterByBreed));
  }, [data, filterByBreed]);

  // 4. Rendering the list of cats
  const renderCatsSearchResult = useCallback(() =&amp;gt; {
    if (isLoading) {
      // 5. Early-return: show loading indicator
      return &amp;lt;CircularProgress /&amp;gt;
      );
    }
    if (!searchResults.length) {
      // 6. Early-return: show empty placeholder
      return &amp;lt;p&amp;gt;No cats found :(&amp;lt;/p&amp;gt;
    }
    // 7. Return actual list component.
    return &amp;lt;CatsList cats={searchResults} /&amp;gt;
  }, [isLoading, searchResults]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;SearchBar
        label="Filter by cat's breed"
                // 8. On each search input change, set `filterByBreed` state.
        onChange={(e) =&amp;gt; setFilterByBreed(e.target.value)}
      /&amp;gt;

      {renderCatsSearchResult()}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s see how can we utilize already known concepts to optimize the INP time.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Issue #1 - List of cats re-renders on each change of the SearchBar component.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As we know from the React’s &lt;a href="https://legacy.reactjs.org/docs/reconciliation.html" rel="noopener noreferrer"&gt;Reconciliation model&lt;/a&gt;, when state changes, React re-renders the components and all its descendants. By calling setFilterByBreed on each SearchBar change, we perform unnecessary re-renders of the expensive CatsList component.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/GhYP7T8mhhI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Instead of triggering onChange immediately, let’s apply debouncing on the SearchBar component.&lt;/p&gt;

&lt;p&gt;You can use a library, or implement a simple debounce utility by yourself. Here’s a simple higher-order-function that triggers callback on timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const DEFAULT_DEBOUNCE_DELAY = 500;

export const debounce = &amp;lt;T,&amp;gt;(
  callback: (value: T) =&amp;gt; void,
  delay = DEFAULT_DEBOUNCE_DELAY
) =&amp;gt; {
  let timer: number;
  return (value: T) =&amp;gt; {
    clearTimeout(timer);
    timer = setTimeout(() =&amp;gt; callback(value), delay);
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A SearchBar component is a simple wrapper around mui’s TextField component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface SearchBarProps extends Partial&amp;lt;TextFieldProps&amp;lt;'standard'&amp;gt;&amp;gt; {}

function SearchBar({ onChange, ...rest }: SearchBarProps) {
  return &amp;lt;TextField onChange={onChange} {...rest} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using debounced onChange:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface SearchBarProps extends Partial&amp;lt;TextFieldProps&amp;lt;'standard'&amp;gt;&amp;gt; {}

function SearchBar({ onChange, ...rest }: SearchBarProps) {
  // Re-initializing debounced onChange on `onChange` change (no pun's intented).
  const debouncedOnChange = useMemo(
    () =&amp;gt; onChange &amp;amp;&amp;amp; debounce(onChange),
    [onChange]
  );

  return &amp;lt;TextField onChange={debouncedOnChange} {...rest} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This improves the INP, because we no longer try to re-render expensive component on each user interaction.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ITOQlrd22R4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We still however, render a long list of elements in the DOM, synchronously.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Issue #2- List renders all elements at once causing low INP score by freezing the layout&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ePFTQlmNMRA"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This time we could:&lt;br&gt;
a) Virtualize the list (by limiting amount of elements attached to the DOM tree, and displaying only such that are visible in the viewport + some offset).&lt;br&gt;
b) Paginate the data (combine intersection-observer with appending next elements to the DOM on user scroll).&lt;/p&gt;

&lt;p&gt;Both solutions allow us to keep initial list lightweight, and get rid of the layout “freeze” that blocks us from interacting with the website. There’s a plenty of resources for implementing both &lt;a href="https://www.patterns.dev/vanilla/virtual-lists/" rel="noopener noreferrer"&gt;Virtualization&lt;/a&gt;, and &lt;a href="https://tanstack.com/query/latest/docs/framework/react/guides/paginated-queries" rel="noopener noreferrer"&gt;Pagination&lt;/a&gt; in React. You can check one of them.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/TqyNA8uXMQ8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;React ≥18, concurrency, and the new mental model of reactivity&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Apart from what I already mentioned, there’s a relatively new approach to reactivity in React using its latest features. By introducing concurrency, we’re now able to transition (no pun intended) from well-known optimization techniques, take the responsibility from the developer, and introduce less intrusive optimizations managed by the tool itself (with a bit of our help).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Non-urgent updates&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With React &amp;lt; 18, every update is urgent. From React 18, every update is urgent by default, but now we’re able to set a low priority on updates that could potentially block the UI.&lt;/p&gt;

&lt;p&gt;Instead of thinking about re-renders, we should start thinking about the urgency of updates we perform.&lt;/p&gt;

&lt;p&gt;Let’s once again go back to our unoptimized cats browser (Fig. 1).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function CatsBreedsBrowserTemplate() {
  ...

  return (
      ...

      &amp;lt;SearchBar
        ...
    // 1. On each search input change, set `filterByBreed` state.
        onChange={(e) =&amp;gt; setFilterByBreed(e.target.value)}
      /&amp;gt;

      ...
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember the part (Fig. 1 [8], Fig. 5 [1]) where, we update the state of the filterByBreed on each SearchBar change? We’ll utilize the useTransition hook to mark this as non-urgent update.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 1. Import the `useTransition` hook.
import { useTransition } from "react";

export function CatsBreedsBrowserTemplate() {
  // 2. Declare `useTransition` fields.
  const [isPending, startTransition] = useTransition();

  ...

  return (
      ...

      &amp;lt;SearchBar
        ...
        onChange={(e) =&amp;gt;
          // 3. Mark state update as non-urgent,
          //    by wrapping it with the `startTransition` function.
          startTransition(() =&amp;gt; {
            setFilterByBreed(e.target.value);
          })
        }
      /&amp;gt;

      ...
  );
}
view raw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Fig 6)&lt;/p&gt;

&lt;p&gt;As simple as that, we were able to eliminate the “freezing” UI issue.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/UE9F1s_WzCY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why this works?‍&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Under the hood, react maintains a queue of updates ready to be performed. Before React 18, all updates were performed sequentially until there were no more updates left. When we update the component’s state, React will queue this update, and perform it until it completes. This however, due to the JavaScript event loop’s “&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop#run-to-completion" rel="noopener noreferrer"&gt;Run-to-completion&lt;/a&gt;” strategy, blocks processing of other tasks (that’s why we can see the UI “freeze” with non-optimized example).&lt;/p&gt;

&lt;p&gt;With the release of React 18, a significant change introduced a new mechanism of processing the updates queue. Instead of forcefully processing an update, React now schedules a unit of update’s work, checks if the browser allows to perform it, and then completes it. Otherwise, it gives the control back to the browser. This way, long-running non-urgent updates no longer blocks the browser’s work that needs to be performed due to the higher priority.&lt;/p&gt;

&lt;p&gt;Transition is only one of the concurrent scheduling mechanisms. Make sure to check all already-existing, and incoming hooks such as &lt;a href="https://react.dev/reference/react/useDeferredValue" rel="noopener noreferrer"&gt;useDeferredValue&lt;/a&gt;, or &lt;a href="https://react.dev/reference/react/use" rel="noopener noreferrer"&gt;use&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Is concurrent scheduling a solution to all problems?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;While the new concurrency model effectively addresses common challenges, it's not a silver bullet applicable to every aspect of your code.&lt;/p&gt;

&lt;p&gt;Since useTransition splits the work in chunks, for expensive-to-render components, splitting the work is impossible. That’s where we need to combine the new concurrent scheduling with already known optimization strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Avoid premature optimization&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Understanding the optimization techniques mentioned and comprehending how the INP calculates the Web Vitals score is crucial for delivering a seamless and responsive user experience for your product.&lt;/p&gt;

&lt;p&gt;While optimization enhances performance, it often comes with associated costs. While it's tempting to immediately implement efficiency improvements, &lt;strong&gt;many enhancements result in higher memory consumption and maintenance challenges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is essential to prioritize simplicity in our solutions. I advise vigilant monitoring of your application to identify areas causing issues and optimizing those selectively, rather than prematurely optimizing, which can introduce unnecessary complexity to your project.&lt;/p&gt;

</description>
      <category>inp</category>
      <category>frontend</category>
      <category>reactnative</category>
      <category>programming</category>
    </item>
    <item>
      <title>Workshop: Build Your Agentic Knowledge Base Using Open-Source Tools</title>
      <dc:creator>Chris Yaowen Zhang</dc:creator>
      <pubDate>Wed, 09 Jul 2025 09:19:57 +0000</pubDate>
      <link>https://dev.to/upsidelab/workshop-build-your-agentic-knowledge-base-using-open-source-tools-47fh</link>
      <guid>https://dev.to/upsidelab/workshop-build-your-agentic-knowledge-base-using-open-source-tools-47fh</guid>
      <description>&lt;p&gt;Hi everyone!&lt;/p&gt;

&lt;p&gt;If you're exploring how to build your own internal “Company GPT”  that runs on your stack, connects to your private data, and doesn’t compromise on control or security, we’re hosting a live hands-on workshop just for that!&lt;/p&gt;

&lt;p&gt;In this workshop, we will use open-source tools, so no black boxes, no vendor lock-in. You’ll set up custom AI agents that can handle repetitive workflows like content generation, product queries, and support requests, all in a few clicks.&lt;/p&gt;

&lt;p&gt;Whether you're curious about LLM-based retrieval, self-hosted agents, or just want to see a clean architecture for internal AI, this is a practical deep dive to get you started fast.&lt;/p&gt;

&lt;p&gt;We will also show you how to ensure reliable answers with layered validation, so your AI agents stay accurate and trustworthy&lt;/p&gt;

&lt;p&gt;By the end of this session, you’ll walk away with:&lt;br&gt;
✅ A fully functional, AI-powered knowledge base&lt;br&gt;
✅ Customizable AI agents trained on your internal content&lt;br&gt;
✅ Ready-to-use code and templates to expand or scale&lt;br&gt;
✅ Live expert guidance (no prior AI experience required)&lt;/p&gt;

&lt;p&gt;⏰ Time: 5:00 PM CET / 11:00 AM ET July 16th&lt;br&gt;
📍 Location: Online&lt;br&gt;
✋ Format: Live workshop with Q&amp;amp;A&lt;br&gt;
🎁 Access to source code and practical templates (&lt;a href="https://github.com/upsidelab/enthusiast" rel="noopener noreferrer"&gt;GitHub link here)&lt;/a&gt;&lt;br&gt;
❗️❗️Note: This is an online event, register to get &lt;a href="https://us02web.zoom.us/webinar/register/WN_ObZfQ43yTseY6wX3TPetMg#/registration" rel="noopener noreferrer"&gt;Zoom link here&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Looking forward to seeing you there!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>agentaichallenge</category>
      <category>ai</category>
      <category>learning</category>
    </item>
    <item>
      <title>Looking for Senior Frontend Engineer (React/Vue)</title>
      <dc:creator>Chris Yaowen Zhang</dc:creator>
      <pubDate>Tue, 08 Jul 2025 08:11:08 +0000</pubDate>
      <link>https://dev.to/upsidelab/looking-for-senior-frontend-engineer-reactvue-3ke4</link>
      <guid>https://dev.to/upsidelab/looking-for-senior-frontend-engineer-reactvue-3ke4</guid>
      <description>&lt;p&gt;Dear Dev community,&lt;/p&gt;

&lt;p&gt;We are hiring a senior frontend engineer. If you are interested, please see below job description or share with people that might be relevant.&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Job description&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://upside.recruitee.com/o/senior-fe-upside" rel="noopener noreferrer"&gt;Upside&lt;/a&gt;, we’re not just shipping features - we’re partnering with ambitious companies across the globe to solve complex, meaningful problems through technology. We work in small, focused teams, combining engineering, product thinking, and design, to deliver outcomes, not just output.&lt;/p&gt;

&lt;p&gt;Open-source is a strategic part of our work. We build and maintain a number of open-source projects that support our clients and internal R&amp;amp;D. Our latest release is &lt;a href="https://github.com/upsidelab/enthusiast" rel="noopener noreferrer"&gt;Enthusiast&lt;/a&gt;, an AI Agent framework tailored for e-commerce use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Role Overview:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We are seeking a Senior Frontend Engineer to drive the design, development, and maintenance of high-quality platforms for global clients. In this role, you will be a key contributor to our client-facing solutions, ensuring performance, scalability, and reliability. You’ll collaborate within a dynamic team of 3 to 9 developers, working together to craft scalable and resilient systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Job requirements&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Extensive commercial experience in building web-based applications.&lt;/li&gt;
&lt;li&gt;In-depth knowledge of modern frontend stack and knowledge of at least one framework (React or Vue).&lt;/li&gt;
&lt;li&gt;Experience with Next.js or other SSR frameworks.&lt;/li&gt;
&lt;li&gt;Experience with GraphQL.&lt;/li&gt;
&lt;li&gt;Solid FP/OOP skills.&lt;/li&gt;
&lt;li&gt;Practical experience with Continuous Integration/Continuous Deployment (CI/CD) practices and modern cloud environments.&lt;/li&gt;
&lt;li&gt;Strong consulting mindset - balancing technical excellence with client needs and communicating effectively with both technical and non-technical stakeholders.&lt;/li&gt;
&lt;li&gt;Fluent English, with clear written and verbal communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bonus Points if you have:&lt;/strong&gt;&lt;br&gt;
Experience in advanced web animations (e.g. framer-motion, GSAP, Rive/Lottie).&lt;/p&gt;

&lt;p&gt;Experience in advanced web technologies such as WebGL, WebSockets, WebAssembly, web workers, and PWA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your responsibilities would be&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developing complex digital platforms - from architecture decisions to implementation.&lt;/li&gt;
&lt;li&gt;Work in autonomous teams (3-9 people) delivering mission-critical systems.&lt;/li&gt;
&lt;li&gt;Collaborate directly with technology leaders, founders, and domain experts to shape solutions that drive real business outcomes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What We Offer&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Transparent progression path.&lt;/li&gt;
&lt;li&gt;Flexible work: office in Kraków, hybrid or remote from anywhere in Poland, with company on-sites.&lt;/li&gt;
&lt;li&gt;Additional paid days off beyond national holidays.&lt;/li&gt;
&lt;li&gt;A deeply collaborative, engineering-led company culture.&lt;/li&gt;
&lt;li&gt;Support for ongoing learning.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Senior Engineer: 20 800 - 25 900 PLN net + VAT&lt;/li&gt;
&lt;li&gt;Lead Engineer: &amp;gt;26 500 PLN net + VAT&lt;/li&gt;
&lt;li&gt;Including additional days off&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Location:&lt;br&gt;
Office in Kraków, hybrid, fully-remote.&lt;/p&gt;

&lt;p&gt;Apply via &lt;a href="https://upside.recruitee.com/o/senior-fe-upside/c/new" rel="noopener noreferrer"&gt;THIS LINK&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>react</category>
      <category>vue</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Rendering HEIC on the Web: How to Make Your Web App Handle iPhone Photos</title>
      <dc:creator>Chris Yaowen Zhang</dc:creator>
      <pubDate>Wed, 02 Jul 2025 15:50:06 +0000</pubDate>
      <link>https://dev.to/upsidelab/rendering-heic-on-the-web-how-to-make-your-web-app-handle-iphone-photos-pj1</link>
      <guid>https://dev.to/upsidelab/rendering-heic-on-the-web-how-to-make-your-web-app-handle-iphone-photos-pj1</guid>
      <description>&lt;p&gt;App developers, your iPhone users are uploading photos, and it’s breaking your app!&lt;/p&gt;

&lt;p&gt;That’s because Apple’s default image format, HEIC, isn’t supported by most browsers.&lt;/p&gt;

&lt;p&gt;We've seen this issue firsthand with quite a few clients that have iOS-heavy user bases. You might not notice it during testing, but the moment a real user uploads a photo from their iPhone, things fall apart.&lt;/p&gt;

&lt;p&gt;In this post, we’ll walk through why it breaks your app, and how to fix it—with practical options for frontend and backend conversion, plus third-party tools. Alright, let's dive in! &lt;/p&gt;

&lt;p&gt;First, let’s render it on our site 🔥&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;img src="/assets/dog.heic" alt=""&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeahhh… You see HEIC, while being highy efficient, is not highly supported in the browser environments. As you can see &lt;a href="https://caniuse.com/?search=HEIC" rel="noopener noreferrer"&gt;here&lt;/a&gt; none of the browsers besides latest releases of Safari support this format.&lt;/p&gt;

&lt;p&gt;Consequently the code snippet above won’t work in most browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why should we care?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Implementing HEIC support in your web app is a time consuming task that often requires revision to make sure that your solution is compatible with your target users’s browsers. If 99% of them use Android then you can probably prioritise other tickets that have been sitting in your backlog.&lt;/p&gt;

&lt;p&gt;Let’s look at some data though. According to Statcounter around 28% of smartphones worldwide are running IOS. Those 28% might want to upload a HEIC image they took with their iPhone to your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bng50yet4qt16i8nrdz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bng50yet4qt16i8nrdz.png" alt="Image mobile operating system market share worldwide"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, most of the businesses are running locally and are limited to a specific region or a country. If your business is targeting the United States market, the share of iOS users on mobile reaches almost 60%. In that case you can be sure that sooner or later a HEIC image will land in your uploader and there has to be a piece of code that will handle it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8emmnov2hz47djlbdo66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8emmnov2hz47djlbdo66.png" alt="Image mobile operating system market share America"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Possible solutions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As you can see the only way to be able to reliably render HEIC in a browser is to convert it into a format that is widely supported. Right now, no one knows if Apple is going to change its licensing and allow other browsers to use HEIC images, and even if it does, waiting for it to happen doesn’t seem like a quick solution.&lt;/p&gt;

&lt;p&gt;Here are a few approaches that I’m going to go through so that you can compare and choose the one that suits your case best:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Converting on the front-end&lt;/li&gt;
&lt;li&gt;Converting on the back-end&lt;/li&gt;
&lt;li&gt;Using third party solutions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Front-end conversion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When searching for HEIC conversion tools you are most likely to stumble upon libheif which is a great open source library written in C. But how do we get to run it with JavaScript in the browser environment?&lt;/p&gt;

&lt;p&gt;There is the &lt;a href="https://github.com/catdad-experiments/libheif-js" rel="noopener noreferrer"&gt;libheif-js&lt;/a&gt; library which lets you directly interact with the precompiled JavaScript libheif or run it using WebAssembly. You can also use it in node.js but I’ll cover that later. However, this is a low level solution that requires you to decode the image yourself and then render it using canvas. You can then preview it with a canvas or convert it into a jpg blob and display it with an img tag. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/strukturag/libheif/blob/gh-pages/index.html" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is an example of how that can be done. As you can see it’s complicated and requires a deep understanding of each step which might not be ideal if you only want to get the job done without getting into details of how libheif works.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the library called &lt;a href="https://github.com/alexcorvi/heic2any" rel="noopener noreferrer"&gt;heic2any&lt;/a&gt; which is using libheif under the hood and makes it super simple to get started with HEIC conversion in the browser. Here’s an example implementation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://t8chgw.csb.app/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhujh3njam5ze9bxkcti.png" alt="HEIC image processor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have a file picker that accepts an HEIC image and previews it using the img tag. As you can see it takes a few lines of code to convert the image and generate a URL which we can then preview like any other static image.&lt;/p&gt;

&lt;p&gt;I think it’s worth elaborating on one thing here…&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Good to know: What is a blob?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You might be asking yourself - what is this whole blob thing in JavaScript? You see it everywhere when dealing with files and sometimes it might not be obvious why it’s being used in the first place.&lt;/p&gt;

&lt;p&gt;Blobs allow you to create file-like objects made out of raw data and a specified MIME type. They are stored in the browser’s memory and can be accessed using a generated url. It is a perfect solution for any image manipulation because Blobs allow for storing the image data and then easily referencing it - in the example above by passing it as a src attribute.&lt;/p&gt;

&lt;p&gt;This video gives a great introduction to how to create a blob yourself and explains a few related concepts like ArrayBuffers:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/ScZZoHj7mqY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;And just like that we managed to handle an HEIC image and view it without using any backend. However, keep in mind that converting images on the client is not an ideal solution and has its drawbacks that shouldn’t be undermined.&lt;/p&gt;

&lt;p&gt;First of all, it’s heavy for the browser to first load the heic2any (2.7 MB) and then to run the actual image conversion. You never know if someone is using your app on the latest MacBook Pro or on the iPhone SE from 2016.&lt;/p&gt;

&lt;p&gt;Besides that, you should never trust the client and handing over such crucial task as converting files to the client might be dangerous.&lt;/p&gt;

&lt;p&gt;In our opinion frontend conversion should be only used for previewing and if you want to upload a file to the server you should send it in the original HEIC format to continue processing on the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Backend conversion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Depending on your stack there are many tools you can use for handling the HEIC conversion on the backend.&lt;/p&gt;

&lt;p&gt;We mentioned libheif before which has dedicated libraries for various languages like Node.js, C#, Python, Rust etc. Let’s take a look at an example implementation in Node.js, specifically using the &lt;a href="https://github.com/catdad-experiments/heic-convert" rel="noopener noreferrer"&gt;heic-convert&lt;/a&gt; package which is an official user friendly wrapper for libheif.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codesandbox.io/p/devbox/bold-architecture-3sjv9k?embed=1&amp;amp;file=%2Findex.js" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rhp91o03hg6ylbofj97.png" alt="HEIC covert Upside"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using Express.js you can quickly create a server that will handle an HEIC file and return the converted jpeg file. Multer is a middleware that lets you easily handle form data and files - I’m using the memory storage here which doesn’t save any file to the disk but with Multer you can also make the files get saved and return the image url that can be accessed multiple times.&lt;/p&gt;

&lt;p&gt;To take it one step further in a production environment you would probably want to save those files to S3 or other storage solution and host them using a CDN.&lt;/p&gt;

&lt;p&gt;In this example you simply get the image data which on the client side can be easily parsed to a blob and rendered. To use our backend implementation in the frontend demo I showed above you simply need to replace the heic2any call with a POST request to the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const formData = new FormData();
formData.append("file", blob);

fetch("http://localhost:4000", {
  method: "POST",
  body: formData,
})
  .then((res) =&amp;gt; res.blob())
  .then((res) =&amp;gt; {
    setPreview(URL.createObjectURL(res));
    setLoading(false);
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you go!&lt;br&gt;
&lt;a href="https://wpjtqn.csb.app/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8g2i09lt0p6uf7uv71p.png" alt="HEIC processor Upside"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have moved the crucial process of converting an image to the backend making it more reliable. Compared to the frontend conversion the performance will be more consistent as it’s always your server that is running the conversion.&lt;/p&gt;

&lt;p&gt;The biggest disadvantage here is the effort that you have to put into spinning up your own server and maintaining it (unless you already have it).&lt;/p&gt;

&lt;p&gt;Overall, if you need to reliably handle HEIC conversion with a prospect of also handling storage and referencing those images in a database, implementing a backend conversion is probably the best way to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Third party solutions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;While implementing the file conversion yourself is not that hard nowadays due to a lot of libraries that get the job done for you, it can still be tricky to choose the right one and be sure that your custom implementation will work well in all the cases.&lt;/p&gt;

&lt;p&gt;For example if your output image seems to have a slightly different color palette, you either have to tweak the parameters to match your expected output (assuming the library you’re using implements such interface) or switch to another library that might or might not solve the issue.&lt;/p&gt;

&lt;p&gt;Also, the solutions I showed in the previous sections do not handle the file metadata. You would need to implement it separately.&lt;/p&gt;

&lt;p&gt;You will also need to perform updates of those libraries and test if the update didn’t break anything in your code.&lt;/p&gt;

&lt;p&gt;If you don’t want to struggle with all those things and just want to get a solution that is easy to implement and always works, you should look for an API that will outsource this process for you. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.convertapi.com/heic-to-jpg" rel="noopener noreferrer"&gt;ConvertAPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.freeconvert.com/api/file-conversion-api" rel="noopener noreferrer"&gt;Free convert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They provide a straightforward documentation and you can always reach out to their support if your business has more specific requirements.&lt;/p&gt;

&lt;p&gt;There are also more profound, end-to-end solutions for handling images which go beyond just image conversion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.filestack.com/" rel="noopener noreferrer"&gt;Filestack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://imgproxy.net/" rel="noopener noreferrer"&gt;Imgproxy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They allow you to upload images, convert them, process them in many different ways like cropping, resizing or applying filters and access using a CDN. You can also specify the storage solution so it doesn’t have to be their servers. Filestack also provides you with an embedded file picker which means you don’t even have to make any requests yourself.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>ios</category>
    </item>
    <item>
      <title>Using GPT for natural language querying</title>
      <dc:creator>Chris Yaowen Zhang</dc:creator>
      <pubDate>Fri, 27 Jun 2025 15:09:48 +0000</pubDate>
      <link>https://dev.to/upsidelab/using-gpt-for-natural-language-querying-1mem</link>
      <guid>https://dev.to/upsidelab/using-gpt-for-natural-language-querying-1mem</guid>
      <description>&lt;p&gt;Year 2023 was supposed to be the year of AI. For sure we all see some changes, especially in social media walls. For example, my LinkedIn is flooded with AI generated texts or images.&lt;br&gt;
However, I prefer to focus on the realms of Artificial Intelligence that are equally fascinating and beneficial. I tried using AI, specifically GPT, to transform natural language into structured, machine-readable forms. This approach is used to generate machine-readable forms that translate into other use cases as well, i.e. the insights in this blog post can inform other equally interesting and useful applications. In this article you will learn how to use GPT for data processing and allowing users to provide natural input to an application. Let’s take a look into some interesting use cases. &lt;/p&gt;

&lt;p&gt;It turns out that GPT does a pretty good job of parsing natural language input into data structures defined by a schema. We can use it for transforming manuals into a JSON graph, that can be then used for, e.g. presenting step-by-step instructions or visualizing dependencies. &lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Example: Let’s cook!&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Example: Let’s cook!&lt;br&gt;
We can use GPT to transform a recipe into a graph, defined by a JSON structure. It can then be used to, for example, present step-by-step instructions or visualizing dependencies between them.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://upsidelab.io/" rel="noopener noreferrer"&gt;Upside&lt;/a&gt; team found a pretty good spaghetti recipe &lt;a href="https://www.bbcgoodfood.com/recipes/best-spaghetti-bolognese-recipe" rel="noopener noreferrer"&gt;available here&lt;/a&gt;.&lt;br&gt;
We can try transforming it into a JSON.&lt;/p&gt;

&lt;p&gt;First, let’s define a &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON schema&lt;/a&gt; of the JSON that we’d like to get from GPT:&lt;/p&gt;

&lt;p&gt;The schema can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://upsidelab.io/recipe.schema.json",
  "title": "Recipe",
  "description": "A recipe definition",
  "type": "object",
  "properties": {
    "ingredients": {
      "description": "The list of ingredients required to prepare the recipe",
      "type": "array",
      "items": {
        "$ref": "#Ingredient"
      }
    },
    "steps": {
      "description": "The list of steps required to prepare the recipe",
      "type": "array",
      "items": {
        "$ref": "#Step"
      }
    }
  },
  "$defs": {
    "ingredient": {
      "$anchor": "Ingredient",
      "type": "object",
      "properties": {
        "id": { "type": "number" },
        "name": { "type": "string" },
        "quantity": { "type": "string" }
      }
    },
    "step": {
      "$anchor": "Step",
      "type": "object",
      "properties": {
        "id": { "type": "number" },
        "name": { "type": "string" },
        "description": { "type": "string" },
        "dependsOnSteps": {
          "type": "array",
          "items": { "type": "number" }
        },
        "dependsOnIngredients": {
          "type": "array",
          "items": { "type": "number" }
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a prompt for GPT. We’ll split it into three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Schema definition&lt;/li&gt;
&lt;li&gt;The recipe to be parsed&lt;/li&gt;
&lt;li&gt;A summary of what we expect it to return (just the JSON, conforming to the schema)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summarize the following recipe into a json that conforms to the following json schema delimited by three backticks

\`
${schemaDefinition}
\`
Use the following recipe, which is delimited by three backticks
\`
${recipe}
\`
Respond with the JSON only. Your output should be parsable by a JSON parser and should conform to the JSON schema definied before.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get stable results, you may need to tweak the prompt. We’ve noticed that more complex domains GPT will often require a more complex prompt that more explicitly specifies our requirements. For a spaghetti recipe it’s enough though.&lt;br&gt;
Sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "ingredients": [
        { "id": 1, "name": "olive oil", "quantity": "1 tbsp" },
        { "id": 2, "name": "smoked streaky bacon", "quantity": "4 rashers" },
        { "id": 3, "name": "medium onions", "quantity": "2" },
        { "id": 4, "name": "carrots", "quantity": "2" },
        { "id": 5, "name": "celery sticks", "quantity": "2" },
        { "id": 6, "name": "garlic cloves", "quantity": "2" },
        { "id": 7, "name": "rosemary sprigs", "quantity": "2-3" },
        { "id": 8, "name": "beef mince", "quantity": "500g" },
        { "id": 9, "name": "plum tomatoes", "quantity": "2 x 400g tins" },
        { "id": 10, "name": "basil", "quantity": "small pack" },
        { "id": 11, "name": "dried oregano", "quantity": "1 tsp" },
        { "id": 12, "name": "bay leaves", "quantity": "2" },
        { "id": 13, "name": "tomato purée", "quantity": "2 tbsp" },
        { "id": 14, "name": "beef stock cube", "quantity": "1" },
        { "id": 15, "name": "red chilli", "quantity": "1 (optional)" },
        { "id": 16, "name": "red wine", "quantity": "125ml" },
        { "id": 17, "name": "cherry tomatoes", "quantity": "6" },
        { "id": 18, "name": "parmesan", "quantity": "75g" },
        { "id": 19, "name": "spaghetti", "quantity": "400g" },
        { "id": 20, "name": "crusty bread", "quantity": "to serve (optional)" }
    ],
    "steps": [
        { 
            "id": 1, 
            "name": "Prepare the saucepan", 
            "description": "Put a large saucepan on a medium heat and add 1 tbsp olive oil.", 
            "dependsOnSteps": [],
            "dependsOnIngredients": [1] 
        },
        { 
            "id": 2, 
            "name": "Cook bacon", 
            "description": "Add 4 finely chopped bacon rashers and fry for 10 mins until golden and crisp.", 
            "dependsOnSteps": [1],
            "dependsOnIngredients": [2] 
        },
        { 
            "id": 3, 
            "name": "Add vegetables and herbs", 
            "description": "Reduce the heat and add the 2 onions, 2 carrots, 2 celery sticks, 2 garlic cloves and the leaves from 2-3 sprigs rosemary, all finely chopped, then fry for 10 mins.", 
            "dependsOnSteps": [2],
            "dependsOnIngredients": [3, 4, 5, 6, 7] 
        },
        { 
            "id": 4, 
            "name": "Brown the mince", 
            "description": "Increase the heat to medium-high, add 500g beef mince and cook stirring for 3-4 mins until the meat is browned all over.", 
            "dependsOnSteps": [3],
            "dependsOnIngredients": [8] 
        },
        { 
            "id": 5, 
            "name": "Add bolognese sauce ingredients", 
            "description": "Add 2 tins plum tomatoes, the finely chopped leaves from ¾ small pack basil, 1 tsp dried oregano, 2 bay leaves, 2 tbsp tomato purée, 1 beef stock cube, 1 deseeded and finely chopped red chilli (if using), 125ml red wine and 6 halved cherry tomatoes. Stir well.", 
            "dependsOnSteps": [4],
            "dependsOnIngredients": [9, 10, 11, 12, 13, 14, 15, 16, 17] 
        },
        { 
            "id": 6, 
            "name": "Simmer the sauce", 
            "description": "Bring to the boil, reduce to a gentle simmer and cover with a lid. Cook for 1 hr 15 mins stirring occasionally, until you have a rich, thick sauce.", 
            "dependsOnSteps": [5],
            "dependsOnIngredients": [] 
        },
        { 
            "id": 7, 
            "name": "Add parmesan and season", 
            "description": "Add the 75g grated parmesan, check the seasoning and stir.", 
            "dependsOnSteps": [6],
            "dependsOnIngredients": [18] 
        },
        { 
            "id": 8, 
            "name": "Cook spaghetti", 
            "description": "When the bolognese is nearly finished, cook 400g spaghetti following the pack instructions.", 
            "dependsOnSteps": [7],
            "dependsOnIngredients": [19] 
        },
        { 
            "id": 9, 
            "name": "Serve", 
            "description": "Drain the spaghetti and either stir into the bolognese sauce, or serve the sauce on top. Serve with more grated parmesan, the remaining basil leaves and crusty bread, if you like.", 
            "dependsOnSteps": [8],
            "dependsOnIngredients": [10, 18, 20] 
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is something we can easily display on a graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv71j1p5uzco5m287ucsh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv71j1p5uzco5m287ucsh.png" alt=" " width="800" height="491"&gt;&lt;/a&gt;&lt;br&gt;
If we aren’t satisfied with the outcome, we can also use a follow-up prompt to mutate the structure. If you look at the recipe, step 8 (cook spaghetti) is independent of the previous steps.&lt;/p&gt;

&lt;p&gt;Let’s ask GPT to change that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 8 doesn't depend on any of the previous steps and can be done in parallel. Change the resulting JSON to reflect that
{
    "ingredients": [
        { "id": 1, "name": "olive oil", "quantity": "1 tbsp" },
        { "id": 2, "name": "smoked streaky bacon", "quantity": "4 rashers" },
        { "id": 3, "name": "medium onions", "quantity": "2" },
        { "id": 4, "name": "carrots", "quantity": "2" },
        { "id": 5, "name": "celery sticks", "quantity": "2" },
        { "id": 6, "name": "garlic cloves", "quantity": "2" },
        { "id": 7, "name": "rosemary sprigs", "quantity": "2-3" },
        { "id": 8, "name": "beef mince", "quantity": "500g" },
        { "id": 9, "name": "plum tomatoes", "quantity": "2 x 400g tins" },
        { "id": 10, "name": "basil", "quantity": "small pack" },
        { "id": 11, "name": "dried oregano", "quantity": "1 tsp" },
        { "id": 12, "name": "bay leaves", "quantity": "2" },
        { "id": 13, "name": "tomato purée", "quantity": "2 tbsp" },
        { "id": 14, "name": "beef stock cube", "quantity": "1" },
        { "id": 15, "name": "red chilli", "quantity": "1 (optional)" },
        { "id": 16, "name": "red wine", "quantity": "125ml" },
        { "id": 17, "name": "cherry tomatoes", "quantity": "6" },
        { "id": 18, "name": "parmesan", "quantity": "75g" },
        { "id": 19, "name": "spaghetti", "quantity": "400g" },
        { "id": 20, "name": "crusty bread", "quantity": "to serve (optional)" }
    ],
    "steps": [
        { 
            "id": 1, 
            "name": "Prepare the saucepan", 
            "description": "Put a large saucepan on a medium heat and add 1 tbsp olive oil.", 
            "dependsOnSteps": [],
            "dependsOnIngredients": [1] 
        },
        { 
            "id": 2, 
            "name": "Cook bacon", 
            "description": "Add 4 finely chopped bacon rashers and fry for 10 mins until golden and crisp.", 
            "dependsOnSteps": [1],
            "dependsOnIngredients": [2] 
        },
        { 
            "id": 3, 
            "name": "Add vegetables and herbs", 
            "description": "Reduce the heat and add the 2 onions, 2 carrots, 2 celery sticks, 2 garlic cloves and the leaves from 2-3 sprigs rosemary, all finely chopped, then fry for 10 mins.", 
            "dependsOnSteps": [2],
            "dependsOnIngredients": [3, 4, 5, 6, 7] 
        },
        { 
            "id": 4, 
            "name": "Brown the mince", 
            "description": "Increase the heat to medium-high, add 500g beef mince and cook stirring for 3-4 mins until the meat is browned all over.", 
            "dependsOnSteps": [3],
            "dependsOnIngredients": [8] 
        },
        { 
            "id": 5, 
            "name": "Add bolognese sauce ingredients", 
            "description": "Add 2 tins plum tomatoes, the finely chopped leaves from ¾ small pack basil, 1 tsp dried oregano, 2 bay leaves, 2 tbsp tomato purée, 1 beef stock cube, 1 deseeded and finely chopped red chilli (if using), 125ml red wine and 6 halved cherry tomatoes. Stir well.", 
            "dependsOnSteps": [4],
            "dependsOnIngredients": [9, 10, 11, 12, 13, 14, 15, 16, 17] 
        },
        { 
            "id": 6, 
            "name": "Simmer the sauce", 
            "description": "Bring to the boil, reduce to a gentle simmer and cover with a lid. Cook for 1 hr 15 mins stirring occasionally, until you have a rich, thick sauce.", 
            "dependsOnSteps": [5],
            "dependsOnIngredients": [] 
        },
        { 
            "id": 7, 
            "name": "Add parmesan and season", 
            "description": "Add the 75g grated parmesan, check the seasoning and stir.", 
            "dependsOnSteps": [6],
            "dependsOnIngredients": [18] 
        },
        { 
            "id": 8, 
            "name": "Cook spaghetti", 
            "description": "When the bolognese is nearly finished, cook 400g spaghetti following the pack instructions.", 
            "dependsOnSteps": [],
            "dependsOnIngredients": [19] 
        },
        { 
            "id": 9, 
            "name": "Serve", 
            "description": "Drain the spaghetti and either stir into the bolognese sauce, or serve the sauce on top. Serve with more grated parmesan, the remaining basil leaves and crusty bread, if you like.", 
            "dependsOnSteps": [7, 8],
            "dependsOnIngredients": [10, 18, 20] 
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the previous response is a part of the follow-up prompt, GPT does a pretty good job at keeping the unchanged elements stable.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;strong&gt;Use case - build a BPMN specification of a business process&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s expand this further - this approach allows us to generate a BPMN definition of a business process, which can subsequently be used by a tool like Camunda.&lt;/p&gt;

&lt;p&gt;Let’s try the following prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate camunda xml file that describes a process delimited by three backticks 

\`
${processDescription}
\`

Respond with the XML only. The XML should contain process definition and a diagram of the process. The XML should be a valid camunda xml file and should be parsable by its modeller tool. There should be nothing except the xml in the response.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where process description could be for example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An invoice is received by a system, parsed by an OCR system, then reviewed by an accountant. After the initial review, it's sent to a manager for an approval. When the approval is made, it's sent to account payable. If the approval is not given by the manager, it is sent to accounting for further clarification.&lt;/p&gt;

&lt;p&gt;After sending that to GPT, we’ll get the following output:&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;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
             xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
             id="Definitions_1"
             targetNamespace="http://bpmn.io/schema/bpmn"&amp;gt;

  &amp;lt;process id="invoiceProcess" name="Invoice Processing" isExecutable="true"&amp;gt;
    &amp;lt;startEvent id="startEvent" name="Invoice Received"&amp;gt;
      &amp;lt;outgoing&amp;gt;Flow_1&amp;lt;/outgoing&amp;gt;
    &amp;lt;/startEvent&amp;gt;

    &amp;lt;sequenceFlow id="Flow_1" sourceRef="startEvent" targetRef="parseInvoice" /&amp;gt;

    &amp;lt;serviceTask id="parseInvoice" name="Parse Invoice with OCR" camunda:type="external" camunda:topic="parseInvoice"&amp;gt;
      &amp;lt;incoming&amp;gt;Flow_1&amp;lt;/incoming&amp;gt;
      &amp;lt;outgoing&amp;gt;Flow_2&amp;lt;/outgoing&amp;gt;
    &amp;lt;/serviceTask&amp;gt;

    &amp;lt;sequenceFlow id="Flow_2" sourceRef="parseInvoice" targetRef="reviewByAccountant" /&amp;gt;

    &amp;lt;userTask id="reviewByAccountant" name="Review by Accountant"&amp;gt;
      &amp;lt;incoming&amp;gt;Flow_2&amp;lt;/incoming&amp;gt;
      &amp;lt;outgoing&amp;gt;Flow_3&amp;lt;/outgoing&amp;gt;
    &amp;lt;/userTask&amp;gt;

    &amp;lt;sequenceFlow id="Flow_3" sourceRef="reviewByAccountant" targetRef="managerApproval" /&amp;gt;

    &amp;lt;exclusiveGateway id="managerApproval" name="Manager Approval"&amp;gt;
      &amp;lt;incoming&amp;gt;Flow_3&amp;lt;/incoming&amp;gt;
      &amp;lt;outgoing&amp;gt;Flow_4&amp;lt;/outgoing&amp;gt;
      &amp;lt;outgoing&amp;gt;Flow_5&amp;lt;/outgoing&amp;gt;
    &amp;lt;/exclusiveGateway&amp;gt;

    &amp;lt;sequenceFlow id="Flow_4" sourceRef="managerApproval" targetRef="sendToAccountPayable" name="Approved"&amp;gt;
      &amp;lt;conditionExpression xsi:type="tFormalExpression"&amp;gt;\${approved}&amp;lt;/conditionExpression&amp;gt;
    &amp;lt;/sequenceFlow&amp;gt;

    &amp;lt;sequenceFlow id="Flow_5" sourceRef="managerApproval" targetRef="sendToAccounting" name="Not Approved"&amp;gt;
      &amp;lt;conditionExpression xsi:type="tFormalExpression"&amp;gt;not(\${approved})&amp;lt;/conditionExpression&amp;gt;
    &amp;lt;/sequenceFlow&amp;gt;

    &amp;lt;userTask id="sendToAccountPayable" name="Send to Account Payable"&amp;gt;
      &amp;lt;incoming&amp;gt;Flow_4&amp;lt;/incoming&amp;gt;
      &amp;lt;outgoing&amp;gt;Flow_6&amp;lt;/outgoing&amp;gt;
    &amp;lt;/userTask&amp;gt;

    &amp;lt;userTask id="sendToAccounting" name="Send to Accounting for Clarification"&amp;gt;
      &amp;lt;incoming&amp;gt;Flow_5&amp;lt;/incoming&amp;gt;
      &amp;lt;outgoing&amp;gt;Flow_7&amp;lt;/outgoing&amp;gt;
    &amp;lt;/userTask&amp;gt;

    &amp;lt;endEvent id="endEvent" name="Process End"&amp;gt;
      &amp;lt;incoming&amp;gt;Flow_6&amp;lt;/incoming&amp;gt;
      &amp;lt;incoming&amp;gt;Flow_7&amp;lt;/incoming&amp;gt;
    &amp;lt;/endEvent&amp;gt;

    &amp;lt;sequenceFlow id="Flow_6" sourceRef="sendToAccountPayable" targetRef="endEvent" /&amp;gt;
    &amp;lt;sequenceFlow id="Flow_7" sourceRef="sendToAccounting" targetRef="endEvent" /&amp;gt;

  &amp;lt;/process&amp;gt;

  &amp;lt;bpmndi:BPMNDiagram id="BPMNDiagram_1"&amp;gt;
    &amp;lt;bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="invoiceProcess"&amp;gt;
      &amp;lt;bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="startEvent"&amp;gt;
        &amp;lt;dc:Bounds x="173" y="102" width="36" height="36" /&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1"&amp;gt;
        &amp;lt;di:waypoint x="209" y="120" /&amp;gt;
        &amp;lt;di:waypoint x="261" y="120" /&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNShape id="ServiceTask_1_di" bpmnElement="parseInvoice"&amp;gt;
        &amp;lt;dc:Bounds x="261" y="80" width="100" height="80" /&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2"&amp;gt;
        &amp;lt;di:waypoint x="361" y="120" /&amp;gt;
        &amp;lt;di:waypoint x="411" y="120" /&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNShape id="UserTask_1_di" bpmnElement="reviewByAccountant"&amp;gt;
        &amp;lt;dc:Bounds x="411" y="80" width="100" height="80" /&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNEdge id="Flow_3_di" bpmnElement="Flow_3"&amp;gt;
        &amp;lt;di:waypoint x="511" y="120" /&amp;gt;
        &amp;lt;di:waypoint x="561" y="120" /&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNShape id="ExclusiveGateway_1_di" bpmnElement="managerApproval" isMarkerVisible="true"&amp;gt;
        &amp;lt;dc:Bounds x="561" y="95" width="50" height="50" /&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNEdge id="Flow_4_di" bpmnElement="Flow_4"&amp;gt;
        &amp;lt;di:waypoint x="611" y="120" /&amp;gt;
        &amp;lt;di:waypoint x="661" y="120" /&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge id="Flow_5_di" bpmnElement="Flow_5"&amp;gt;
        &amp;lt;di:waypoint x="586" y="145" /&amp;gt;
        &amp;lt;di:waypoint x="586" y="195" /&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNShape id="UserTask_2_di" bpmnElement="sendToAccountPayable"&amp;gt;
        &amp;lt;dc:Bounds x="661" y="80" width="100" height="80" /&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNShape id="UserTask_3_di" bpmnElement="sendToAccounting"&amp;gt;
        &amp;lt;dc:Bounds x="536" y="195" width="100" height="80" /&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNEdge id="Flow_6_di" bpmnElement="Flow_6"&amp;gt;
        &amp;lt;di:waypoint x="761" y="120" /&amp;gt;
        &amp;lt;di:waypoint x="811" y="120" /&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge id="Flow_7_di" bpmnElement="Flow_7"&amp;gt;
        &amp;lt;di:waypoint x="586" y="275" /&amp;gt;
        &amp;lt;di:waypoint x="586" y="325" /&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="endEvent"&amp;gt;
        &amp;lt;dc:Bounds x="811" y="102" width="36" height="36" /&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
    &amp;lt;/bpmndi:BPMNPlane&amp;gt;
  &amp;lt;/bpmndi:BPMNDiagram&amp;gt;
&amp;lt;/definitions&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s9zr5tv8j6z6mqxbs6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s9zr5tv8j6z6mqxbs6q.png" alt=" " width="800" height="358"&gt;&lt;/a&gt;&lt;br&gt;
Looking good!&lt;/p&gt;

&lt;p&gt;Remarkable insights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GPT seems to omit some of the XMLNS references quite often. This can be easily fixed in post-processing, which may be way more cost-effective than regenerating the response.&lt;/li&gt;
&lt;li&gt;I once got a response where the diagram definition looked like the following:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="invoiceProcess"&amp;gt;
    &amp;lt;!-- Diagram layout omitted for brevity --&amp;gt;
&amp;lt;/bpmndi:BPMNPlane&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Even though it’s quite amusing, this is an edge case that we’d likely need to support if aiming for production use. The most common approaches are to add more validations and retries to our application’s code, improve the prompt to direct GPT, or try different temperatures of the model. &lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Use case - build dynamic forms in React&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Another common use case that we can try to automate is building custom forms dynamically. Many applications, such as surveys, custom forms etc., rely on this feature to gain feedback from its candidates, customers or potential leads. &lt;/p&gt;

&lt;p&gt;GPT makes it incredibly easy to build a tool that allows a regular user to create such a form by providing natural language input. Let’s use a library called &lt;a href="https://rjsf-team.github.io/react-jsonschema-form/docs/" rel="noopener noreferrer"&gt;react-jsonschema-form&lt;/a&gt;. It’s a React-based library that generates forms based on a JSON definition. It also contains an extensive schema definition that such JSONs can be validated against.&lt;/p&gt;

&lt;p&gt;We can leverage the fact that GPT was trained on a large dataset that also includes definitions of commonly used JSON schemas. That means we don’t have to provide the exact schema, and we can refer to it in our prompt by name. &lt;/p&gt;

&lt;p&gt;Let’s create a prompt that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Specifies that we would like to use the schema of react-jsonschema-form&lt;/li&gt;
&lt;li&gt;Include the user's definition of the ideal form&lt;/li&gt;
&lt;li&gt;Will specify some additional criteria for the output (i.e. make sure that we only get a JSON as a response)
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate a JSON definition that follows the schema of react-jsonschema-form. Use the following form that is delimited by three backticks

\`
${userInput}
\`

Respond with the JSON only and don't include any additional characters. Make sure that the output is a valid JSON and that it passes validation with the @rjsf/validator-ajv8 validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Where userInput could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The form should allow a customer to provide an order number (starting with letter R followed by 9 digits), customer's phone number (limited only to country code and digits) and a customer support request. There should also be a field for selecting the type of the issue from a list of the following options: order missing, change in order, return request. At the end of the form, there should be a checkbox labeled "I agree to the privacy policy of company X"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case GPT gets back to us with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "Customer Support Form",
  "type": "object",
  "required": [
    "orderNumber",
    "phoneNumber",
    "supportRequest",
    "issueType",
    "privacyPolicyAgreement"
  ],
  "properties": {
    "orderNumber": {
      "type": "string",
      "title": "Order Number",
      "pattern": "R[0-9]{9}"
    },
    "phoneNumber": {
      "type": "string",
      "title": "Phone Number",
      "pattern": "\\+[0-9]+"
    },
    "supportRequest": {
      "type": "string",
      "title": "Customer Support Request"
    },
    "issueType": {
      "type": "string",
      "title": "Type of Issue",
      "enum": [
        "order missing",
        "change in order",
        "return request"
      ]
    },
    "privacyPolicyAgreement": {
      "type": "boolean",
      "title": "I agree to the privacy policy of company X",
      "const": true
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can quickly verify it in react-jsonschema-form’s playground: &lt;a href="https://rjsf-team.github.io/react-jsonschema-form/" rel="noopener noreferrer"&gt;https://rjsf-team.github.io/react-jsonschema-form/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frasfxirzqgij46jcmgwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frasfxirzqgij46jcmgwz.png" alt=" " width="800" height="615"&gt;&lt;/a&gt;&lt;br&gt;
As you can see, it correctly generated a form, together with relevant validations. As with previous examples, we can make modifications by follow-up prompts.&lt;/p&gt;

&lt;p&gt;Let’s try making the privacy policy field selectable by default. Sending the following prompt:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;The privacy policy agreement should be checked by default&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Will result in a new JSON definition of the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "Customer Support Form",
  "type": "object",
  "required": ["orderNumber", "phoneNumber", "supportRequest", "issueType"],
  "properties": {
    "orderNumber": {
      "type": "string",
      "title": "Order Number",
      "pattern": "R[0-9]{9}"
    },
    "phoneNumber": {
      "type": "string",
      "title": "Phone Number",
      "pattern": "\\+[0-9]+"
    },
    "supportRequest": {
      "type": "string",
      "title": "Customer Support Request"
    },
    "issueType": {
      "type": "string",
      "title": "Type of Issue",
      "enum": ["order missing", "change in order", "return request"]
    },
    "privacyPolicyAgreement": {
      "type": "boolean",
      "title": "I agree to the privacy policy of company X",
      "default": true
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which looks like that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucsa1ovc5n9pxol86oaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucsa1ovc5n9pxol86oaw.png" alt=" " width="800" height="471"&gt;&lt;/a&gt;&lt;br&gt;
As you can see, the checkbox is now selected by default. Similar to that, we can make further tweaks to the existing form.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Use case - build dynamic UIs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the previous example, we were bound to forms, which are useful but limited in terms of what we can do. To build more general UIs dynamically, we can use a tool called react-json-schema, combine it with GPT, and build fully dynamic complex UIs.&lt;/p&gt;

&lt;p&gt;To start with, let’s define some components. We’ll use &lt;a href="https://react-bootstrap.netlify.app/" rel="noopener noreferrer"&gt;react-bootstrap&lt;/a&gt; to simplify the UI work needed.&lt;/p&gt;

&lt;p&gt;Let’s make a wrapper component for the whole app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function AppRoot({ children }) {
  return (
    &amp;lt;&amp;gt;
      {children}
    &amp;lt;/&amp;gt;
  )
}

export default AppRoot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a basic layout component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Container } from 'react-bootstrap';

function AppMainLayout({ children }) {
  return (
    &amp;lt;Container&amp;gt;
      {children}
    &amp;lt;/Container&amp;gt;
  )
}

export default AppMainLayout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A customizable navbar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';

function AppNavbar({ title, links }) {
  return (
    &amp;lt;Navbar bg="primary" data-bs-theme="dark"&amp;gt;
    &amp;lt;Container&amp;gt;
      &amp;lt;Navbar.Brand href="/"&amp;gt;{title}&amp;lt;/Navbar.Brand&amp;gt;
      &amp;lt;Nav className="me-auto"&amp;gt;
        {links &amp;amp;&amp;amp; links.map((link) =&amp;gt; {
          return &amp;lt;Nav.Link href={link.url} key={link.title}&amp;gt;{link.title}&amp;lt;/Nav.Link&amp;gt;
        })}
      &amp;lt;/Nav&amp;gt;
    &amp;lt;/Container&amp;gt;
  &amp;lt;/Navbar&amp;gt;
  )
}

export default AppNavbar;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A toolbar with some action buttons (that we can wire together later):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Button from 'react-bootstrap/Button';

function AppToolbar() {
  return (
    &amp;lt;div className="py-2"&amp;gt;
      &amp;lt;Button variant="primary" className="mx-1"&amp;gt;New user&amp;lt;/Button&amp;gt;
      &amp;lt;Button variant="secondary" className="mx-1"&amp;gt;Refresh&amp;lt;/Button&amp;gt;
      &amp;lt;Button variant="secondary" className="mx-1"&amp;gt;Deactivate&amp;lt;/Button&amp;gt;
      &amp;lt;Button variant="secondary" className="mx-1"&amp;gt;Reset password&amp;lt;/Button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default AppToolbar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a table for displaying a list of users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Table from 'react-bootstrap/Table'

function AppUserList() {
  return (
    &amp;lt;Table striped bordered hover&amp;gt;
      &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;th&amp;gt;#&amp;lt;/th&amp;gt;
          &amp;lt;th&amp;gt;Email&amp;lt;/th&amp;gt;
          &amp;lt;th&amp;gt;First Name&amp;lt;/th&amp;gt;
          &amp;lt;th&amp;gt;Last Name&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
      &amp;lt;/thead&amp;gt;
      &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;td&amp;gt;1&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;test@example.com&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;John&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;Doe&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;td&amp;gt;2&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;jack@example.com&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;Jack&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;Dummy&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;td&amp;gt;3&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;thomas@example.com&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;Tomas&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;Jonas&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;td&amp;gt;4&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;paul@example.com&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;Paul&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;Test&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
      &amp;lt;/tbody&amp;gt;
    &amp;lt;/Table&amp;gt;
  )
}

export default AppUserList
view raw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect it all together in our web application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
import ReactJsonSchema from 'react-json-schema';
import AppNavbar from './components/AppNavbar'
import AppRoot from './components/AppRoot'
import AppMainLayout from './components/AppMainLayout'
import AppToolbar from './components/AppToolbar'
import AppUserList from './components/AppUserList'

const uiRenderer = new ReactJsonSchema();
uiRenderer.setComponentMap({ AppRoot, AppNavbar, AppMainLayout, AppToolbar, AppUserList });

const uiSchema = await fetchGPTResponse(prompt);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  uiRenderer.parseSchema(uiSchema)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s easy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initialize react-json-schema parser&lt;/li&gt;
&lt;li&gt;Configure it to recognize our custom components&lt;/li&gt;
&lt;li&gt;Prompt GPT for a schema based on user’s description&lt;/li&gt;
&lt;li&gt;Render the schema that GPT responded with&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now it’s time to define the actual prompt to be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate a JSON that follows the react-json-schema format for the following set of components and their props delimited by three backticks:
\`
AppRoot (children: Node[]) , AppNavbar (title: string, links: Link[]), AppMainLayout (children: Node[]) , AppToolbar (), AppUserList (), h1 (text: string), h2 (text: string)
\`
The following custom types are defined in the application
\`
type Link = {
title: string;
url: string;
}
\`

The JSON should build a UI following a specification delimited by three backticks
\`
The UI should start with a navbar, a title specifying that this is a dashboard for managing users. Below the title there should be a more detailed description of the dashboard. The main element of the UI should be a table with a list of users. Above the table there should be a toolbar allowing to modify the list of users.
\`
The UI must be wrapped in the AppRoot component. Specify the props directly in the same object as the component, without an additional props object. Only use the props that are defined above. The output should be a valid react-json-schema specification. Don't output any additional characters.

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Remarkable insights:&lt;/strong&gt; As you can see, we started with a list of available components, followed by an additional list of props available for each of them. My tests have shown that it’s also helpful to explicitly state how to provide props (without a nested props object). Without that remark, GPT put it inside a nested object, which didn’t work with react-json-schema library. My assumption is that react-json-schema is not a very popular library and lacks extensive examples online - hence the need to point GPT in the right direction.&lt;/p&gt;

&lt;p&gt;A sample GPT response will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "component": "AppRoot",
  "children": [
    {
      "component": "AppNavbar",
      "title": "User Management Dashboard",
      "links": [
        {
          "title": "Home",
          "url": "/"
        },
        {
          "title": "Settings",
          "url": "/settings"
        }
      ]
    },
    {
      "component": "AppMainLayout",
      "children": [
        {
          "component": "h1",
          "text": "User Management Dashboard"
        },
        {
          "component": "h2",
          "text": "Manage and oversee user accounts and permissions"
        },
        {
          "component": "AppToolbar",
          "children": []
        },
        {
          "component": "AppUserList",
          "children": []
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PICS Which renders a quite decent dashboard! GPT was able to infer that the majority of the layout should go inside a component called AppMainLayout, but didn’t put the AppNavbar there. It also filled the navbar with some example links.&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  Use case - querying an ElasticSearch index
&lt;/h2&gt;

&lt;p&gt;ElasticSearch is commonly used for building indexes of products or application logs. We can use GPT to translate user’s queries to an ElasticSearch query.&lt;/p&gt;

&lt;p&gt;The approach would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Specify that we’d like to get an ElasticSearch query as a result&lt;/li&gt;
&lt;li&gt;Provide information about the structure of the index&lt;/li&gt;
&lt;li&gt;Provide information about the user's query&lt;/li&gt;
&lt;li&gt;Specify additional criteria for the output&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example: We have an ElasticSearch index with the following definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "products_production": {
    "aliases": {},
    "mappings":{
      "spree_product": {
        "properties": {
          "available_on": {
            "type":"date",
            "format":"dateOptionalTime"
          },
          "brand_id": {
            "type":"keyword"
          },
          "created_at": {
            "type":"date",
            "format":"dateOptionalTime"
          },
          "description": {
            "type":"text",
            "analyzer":"snowball"
          },
          "discontinue_on": {
            "type":"date",
            "format":"dateOptionalTime"
          },
          "featured": {
            "type":"boolean"
          },
          "name": {
            "type":"text",
            "boost":100.0,
            "fields": {
              "untouched": {
                "type":"keyword"
              }
            },
            "analyzer":"snowball"
          },
          "price": {
            "type":"double"
          },
          "sku": {
            "type":"keyword"
          },
          "suggestion_terms": {
            "type":"text",
            "analyzer":"simple"
          },
          "taxon_ids": {
            "type":"keyword"
          }
        }
      }
    },
    "settings": {
      "index": {
        "number_of_shards":"1",
        "provided_name":"products_production",
        "creation_date":"1684177003621",
        "analysis": {
          "filter": {
            "nGram_filter": {
              "token_chars": [
                "letter",
                "digit",
                "punctuation",
                "symbol"
              ],
              "min_gram": "3",
              "type": "nGram",
              "max_gram": "4"
            }
          },
          "analyzer": {
            "nGram_analyzer": {
              "filter":[
                "lowercase",
                "asciifolding",
                "nGram_filter"
              ],
              "type": "custom",
              "tokenizer": "whitespace"
            },
            "whitespace_analyzer": {
              "filter": [
                "lowercase",
                "asciifolding"
              ],
              "type": "custom",
              "tokenizer": "whitespace"
            }
          }
        },
        "number_of_replicas": "1",
        "uuid": "WV8HWbS_QsudGKL223hwrA",
        "version": {
          "created": "6080091"
        }
      }
    }
  }
}
Create an elasticsearch query json that operates on an index with the following definition delimited by three backticks
\`
${indexDefinition}
\`

The query should return the results described here delimited by three backticks:
\`
Return the list of the most expensive products that are currently available, aren't featured and don't have any taxons associated with them.
\`

Return only a JSON and make sure it can be interpreted by an elasticsearch 6.8 instance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s worth mentioning that this approach should never be used on an ElasticSearch that’s not meant to be fully accessible by the user due to the potential risk of making arbitrary queries to the cluster.&lt;br&gt;
It comes back with the desired query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "available_on": {
              "lte": "now"
            }
          }
        },
        {
          "term": {
            "featured": false
          }
        },
        {
          "bool": {
            "must_not": {
              "exists": {
                "field": "taxon_ids"
              }
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now pass it to ElasticSearch and get the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "took": 51,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 435,
        "max_score": null,
        "hits": [
            {
                "_index": "products_production",
                "_type": "product",
                "_id": "1694",
                "_score": null,
                "_source": {
                    "name": "Asus ROG Zephyrus S Ultra Slim Gaming Laptop, 15.6\u0026quot; 144Hz IPS Type FHD, GeForce RTX 2070, Intel Core i7-9750H, 16GB DDR4, 512GB PCIe Nvme SSD, Aura Sync RGB, Windows 10, GX531GW-AB76",
                    "description": "\u003cp\u003eNvidia GeForce RTX 2070 8GB DDR6 (base: 1080 MHz, Boost: 1305 MHz; TDP: 90W). 230W Power Adaptor. Support Type-c PD 3.0 up to 65W Latest Intel Core i7-9750h Hexa-Core processor; Bluetooth 5.0 15.6\u0026rdquo; 144Hz 3ms IPS-Type full HD (1920x1080) Display 16GB 2666Hz DDR4 | 512GB PCIe NVMe SSD | Windows 10 Home 0.62\u0026rdquo; thin, 4.6 lbs ultraportable military-grade magnesium alloy body with premium metal cover Rog active Aerodynamic System (AAs) with upgraded 12V fans and anti-dust tunnels to preserve cooling performance and system stability Gigabit wave 2 Wi-Fi 5 (802.11AC) | customizable 4-zone ASUS Aura RGB Gaming Keyboard The all new ROG Zephyrus s gaming Laptop is redefining ultra-slim gaming laptops yet again with innovative engineering to cool Its 9th Gen Intel Core i7 processor and GeForce RTX Max-Q graphics, so you can immerse yourself in its no-compromise 144Hz/3ms Display. *The actual transfer speed of USB 3.0, 3.1 (Gen 1 and 2), and/or Type-C will vary depending on many factors including the processing speed of the host device, file attributes and other factors related to system configuration and your operating environment.\u003c/p\u003e",
                    "taxon_ids": [],
                    "brand_id": null,
                    "available_on": "2019-12-02T00:00:00.000Z",
                    "discontinue_on": null,
                    "featured": false,
                    "price": "1595.73",
                    "created_at": "2019-12-02T20:07:26.594Z",
                    "suggestion_terms": ["Asus ROG Zephyrus S Ultra Slim Gaming Laptop, 15.6\u0026quot; 144Hz IPS Type FHD, GeForce RTX 2070, Intel Core i7-9750H, 16GB DDR4, 512GB PCIe Nvme SSD, Aura Sync RGB, Windows 10, GX531GW-AB76"]
                },
                "sort": [1595.73]
            },  
            {
                "_index": "products_production",
                "_type": "product",
                "_id": "1470",
                "_score": null,
                "_source": {
                    "name": "Weber - Genesis II S-435 62006001 Stainless Steel",
                    "description": "\u003cp\u003eA grill that has it all. A powerful grilling engine, impressive features, and an outstanding 10 year warranty on all parts of the grill to back it up. The immense space and sear station will execute as many steaks as you need to perfection, while the side burner saut\u0026eacute;s veggies. Premium stainless steel cooking grates retain even heat and are easy to clean. The Tuck-Away warming rack keeps food warm while the main dish grills underneath. And between cookouts, store your grilling tools and accessories in the enclosed grill cabinet. Those are just a few features that set the Genesis II S-435 apart from your typical grill.\u003c/p\u003e\r\n\u003cul\u003e\r\n\u003cli\u003ePropane Grill fueled by propane\u003c/li\u003e\r\n\u003cli\u003eElectric Ignition Electronic ignition makes it easy to start\u003c/li\u003e\r\n\u003cli\u003eSide Burner Side burner for cooking versatility.\u003c/li\u003e\r\n\u003cli\u003e9,000 BTU-per-hour input Sear Station burner\u003c/li\u003e\r\n\u003cli\u003eStainless steel heat deflectors\u003c/li\u003e\r\n\u003cli\u003eStainless steel lid with thermometer\u003c/li\u003e\r\n\u003cli\u003eEnclosed cabinet with stainless steel doors\u003c/li\u003e\r\n\u003cli\u003eSix tool hooks\u003c/li\u003e\r\n\u003cli\u003eTwo heavy-duty front locking swivel casters and two large wheels\u003c/li\u003e\r\n\u003c/ul\u003e",
                    "sku": "62006001",
                    "taxon_ids": [],
                    "brand_id": null,
                    "available_on": "2019-11-03T00:00:00.000Z",
                    "discontinue_on" :"2019-11-04T00:00:00.000Z", 
                    "featured": false,
                    "price": "1315.5",
                    "created_at": "2019-11-03T19:02:48.694Z",
                    "suggestion_terms": ["Weber - Genesis II S-435 62006001 Stainless Steel"]
                },
                "sort" :[1315.5]
            },
            // more results here
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Follow-up use case - building a query in a query language that the user may not know&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This feature is useful when a user needs to query a tool with its own specific query language or with a structure that the user doesn’t know. AWS seems to be running an A/B test of a feature where you can generate a CloudWatch search query based on a natural language input.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Use case - data visualization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Interesting use case - you allow users to pull report data (by e.g. querying an ElasticSearch index or a database). This is often combined with visualizing the data. Could we allow the user to also define how they’d like the data presented? Let’s say a manager needs a pie chart for their PowerPoint presentation.&lt;/p&gt;

&lt;p&gt;Fortunately, there’s a tool called &lt;a href="https://vega.github.io/vega/" rel="noopener noreferrer"&gt;Vega&lt;/a&gt; which is a visualization grammar that allows you to define graphs using a JSON schema. It also comes with &lt;a href="https://vega.github.io/vega-lite/" rel="noopener noreferrer"&gt;Vega-Lite&lt;/a&gt;, which allows us to easily render these charts using JavaScript. There’s also an &lt;a href="https://vega.github.io/editor/#/examples/vega-lite/" rel="noopener noreferrer"&gt;interactive playground&lt;/a&gt; that we can use for testing our diagrams.&lt;/p&gt;

&lt;p&gt;Let’s take &lt;a href="https://vega.github.io/editor/data/stocks.csv" rel="noopener noreferrer"&gt;a sample CSV file from Vega-Lite’s demo&lt;/a&gt; that contains the stock prices history of a few tech companies. It will be easy for us to reference it on the playground.&lt;/p&gt;

&lt;p&gt;The file is a regular CSV that contains a few columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;symbol,date,price
MSFT,Jan 1 2000,39.81
MSFT,Feb 1 2000,36.35
MSFT,Mar 1 2000,43.22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s try to generate some charts that utilize this structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate a JSON using vega-lite schema that takes input data from file data/stocks.csv. The visualization should work on a dataset with the following columns delimited by three backticks
\`
symbol date price
\`
The visualization should result in the following result delimited by three backticks
\`
I would like to get a line chart of each of the stocks over time. There should also be a second line chart that shows only MSFT stock. Finally, I would like to see a pie chart of the average stock prices for each symbol
\`

Return only the resulting JSON and don't include any additional characters. Make sure that the visualization conforms to the vega-lite schema.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After trying it out with GPT we get the following definition&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {"url": "data/stocks.csv"},
  "vconcat": [
    {
      "title": "Stock Prices Over Time",
      "mark": "line",
      "encoding": {
        "x": {"field": "date", "type": "temporal", "title": "Date"},
        "y": {"field": "price", "type": "quantitative", "title": "Price"},
        "color": {"field": "symbol", "type": "nominal", "title": "Symbol"}
      }
    },
    {
      "title": "MSFT Stock Price Over Time",
      "transform": [{"filter": "datum.symbol === 'MSFT'"}],
      "mark": "line",
      "encoding": {
        "x": {"field": "date", "type": "temporal", "title": "Date"},
        "y": {"field": "price", "type": "quantitative", "title": "Price"}
      }
    },
    {
      "title": "Average Stock Prices",
      "transform": [{"aggregate": [{"op": "mean", "field": "price", "as": "average_price"}], "groupby": ["symbol"]}],
      "mark": "arc",
      "encoding": {
        "theta": {"field": "average_price", "type": "quantitative", "title": "Average Price"},
        "color": {"field": "symbol", "type": "nominal", "title": "Symbol"}
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which gives us the desired set of charts&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5lw3x9cro7m70xbypf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5lw3x9cro7m70xbypf9.png" alt=" " width="800" height="566"&gt;&lt;/a&gt;&lt;br&gt;
There’s also &lt;a href="https://github.com/ObservedObserver/viz-gpt/" rel="noopener noreferrer"&gt;an interesting project on Github&lt;/a&gt; that showcases a similar concept. Their &lt;a href="https://dev.tobase%20prompt"&gt;base prompt&lt;/a&gt; looks slightly differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a great assistant at vega-lite visualization creation. No matter what the user asks, you should always respond with a valid vega-lite specification in JSON.

You should create the vega-lite specification based on user's query.

Besides, Here are some requirements:
1. Do not contain the key called 'data' in vega-lite specification.
2. If the user ask many times, you should generate the specification based on the previous context.
3. You should consider to aggregate the field if it is quantitative and the chart has a mark type of react, bar, line, area or arc.
4. Consider to use bin for field if it is a chart like heatmap or histogram.
5. The available fields in the dataset and their types are:
${metas
    .map((field) =&amp;gt; `${field.name} (${field.semanticType})`)
    .join(", ")}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Good practices - learn from &lt;a href="https://upsidelab.io/" rel="noopener noreferrer"&gt;the Upside team&lt;/a&gt;'s experiences&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once the request was submitted to ChatGPT, we should run a JSON schema (or XML) validator on the response it provides. Despite its reliability, it may act differently than expected. Sometimes a retry will be enough to get a result that’s consistent with the defined structure. On the user experience side, there are several approaches, but you should tailor them to your specific needs.  A good example of JSON schema validation is using Vega-Lite’s JSON Schema, available &lt;a href="https://vega.github.io/schema/vega-lite/v5.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can apply the same method with any JSON schema you create, like for a spaghetti recipe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complexity - try not to bite more than GPT can chew‍&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The prompts can’t be too long, as GPT may lose information and context with complicated inputs. You can use clever prompting engineering to break a task into smaller ones that gradually reduces the cognitive load and follows a logical reasoning process. For example, instead of asking GPT to format a recipe in a specific JSON schema, at first break it down into steps “Break this recipe down into steps". Once that’s achieved - "Structure this as the following JSON schema". It’s about breaking it down into steps, where each step builds on the understanding of the previous one and aligns it with the defined structure. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance - being patient is the key‍&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generating outputs with GPT is quite time-consuming, especially if we need to process multiple prompts in order to achieve the desired use case. We can expect improvements when it comes to hardware, so this won’t always be an issue, but for now, we need to focus on the UX level to cover up these issues. Improving UX in such cases is challenging, and often the best solution is to stream the response in smaller parts. However, if the model needs to return a full result (e.g. a JSON schema), this approach doesn’t work.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost - manage your expenses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, using e.g. GPT-4 can be quite costly at scale. I wouldn’t be too worried about that in the long term, as like any technology, it will get cheaper over time, but for now it’s good to keep an eye on how much money is spent. The most sensible approach is to perform as much preprocessing as possible before pushing input to GPT, in order to limit the cost of the actual GPT usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability - fine-tuning for diverse inputs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Playing around with these examples makes it quite easy to get it to a point where it fairly consistently delivers a great result. However, once you launch a GPT-based feature to a wider audience that will use it with different input data, you may need plenty of fine-tuning in order to ensure the solution's reliability. You never really know what the user’s will type into the chat box once you deploy your app. As developers of the feature, we are usually biased. User testing those solutions is important, especially since using a chat interface to solve actual problems (rather than just getting answers) is still a pretty novel idea and people have different expectations. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt injection risks - always double check&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It goes without saying that you need to be careful about the responses it may generate // (Optional) Screenshot of DPD example (the one where a customer prompted it to write a song about a shitty delivery company) ![[dpd_chatbot_fail-1.png]]&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;GPT does a pretty good job in parsing data. Basing on the experiments described in the post, we can define a simple framework that can handle diverse input and output formats:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Specify the desired schema&lt;/li&gt;
&lt;li&gt;Specify the details of available fields (when needed)&lt;/li&gt;
&lt;li&gt;Specify the input data&lt;/li&gt;
&lt;li&gt;Reinforce important details (e.g. that the response should be a JSON and only a JSON)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even now, there are many applications that can utilize GPT, without being AI-first. It doesn’t need to be a whole GPT-based experience, it can supplement the existing experiences and improve user productivity.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>nlp</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Limiting Data Egress: Challenges, Solutions, and Best Practices</title>
      <dc:creator>Chris Yaowen Zhang</dc:creator>
      <pubDate>Wed, 28 May 2025 14:52:04 +0000</pubDate>
      <link>https://dev.to/upsidelab/limiting-data-egress-41nd</link>
      <guid>https://dev.to/upsidelab/limiting-data-egress-41nd</guid>
      <description>&lt;p&gt;&lt;strong&gt;Long story Short or simply put -  Introduction&lt;/strong&gt;&lt;br&gt;
In today's interconnected digital landscape, data flows freely across borders, networks, and platforms, driving innovation, powering economies, and shaping industries. However, amid this digital renaissance, &lt;strong&gt;data egress&lt;/strong&gt; emerges as a critical consideration for businesses navigating the complex terrain of data management and compliance.&lt;/p&gt;

&lt;p&gt;Generally speaking, data egress, which is the movement of data out of a controlled environment, poses multifaceted challenges and opportunities for organizations across various sectors. From ensuring regulatory compliance to safeguarding intellectual property and &lt;strong&gt;maintaining data sovereignty&lt;/strong&gt;, the implications of data egress are far-reaching and demand meticulous attention.&lt;/p&gt;

&lt;p&gt;To address these challenges, we will use the &lt;strong&gt;&lt;a href="https://healthdatanexus.ai/" rel="noopener noreferrer"&gt;Health Data Nexus&lt;/a&gt;&lt;/strong&gt; as an example of a strategy we have implemented for the Temerty Centre for Artificial Intelligence Research and Education in Medicine (T-CAIREM) at the University of Toronto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Health Data Nexus?&lt;/strong&gt;&lt;br&gt;
The Health Data Nexus is the infrastructure deployed to manage the data and analytical environments for advancing health through AI at T-CAIREM. The platform itself stores &lt;strong&gt;de-identified&lt;/strong&gt; data provided by various parties to help facilitate new advancements in healthcare.&lt;/p&gt;

&lt;p&gt;While we could just share de-identified medical data by sending files to the parties that would like to do research on them, it is not the best approach. In such a scenario, we would not have much control over the files themselves. Instead, we have decided to build a platform deployed to &lt;strong&gt;Google Cloud&lt;/strong&gt;, that allows credentialed users to access and work on the data but only within the boundaries that are being set by cloud environments that we provide. That approach, besides making it easier for us to manage the process more efficiently, also makes it possible to introduce additional security measures against malicious actions taken on data.&lt;/p&gt;

&lt;p&gt;From the user’s perspective, accessing the datasets is really simple - they go to the web platform, request for access to data, then spin up a dedicated server in the cloud for their research. That server gives a limited &lt;strong&gt;Jupyter/RStudio interface&lt;/strong&gt; that also implements access controls. The user then gets access to the data via a web interface and can execute their own scripts that use the dataset.&lt;/p&gt;

&lt;p&gt;When the workbench with a predefined environment is up and ready, all the data is being fused into the working space directly from a cloud storage service. At that point, the user has access to the health data that is stored by the service itself. With great power comes great responsibility, one would say.&lt;/p&gt;

&lt;p&gt;While we trust the users of our platform to have good intentions, and even if the aforementioned data is anonymized, we would like to minimize the possibility of the data getting outside of the platform. A merely accidental leakage of data may happen during work, for instance with a remote version controls by committing the files to a public repository. &lt;/p&gt;

&lt;p&gt;With that in mind, let’s see what are the solutions that we can implement to limit the risk of data leaking outside of the platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General solution graph:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2enebsrguwl3lsbcdwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2enebsrguwl3lsbcdwt.png" alt=" " width="592" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easy, not so reliable, a little bit hacky but still worth mentioning&lt;/strong&gt;&lt;br&gt;
There are a few simple, rather obvious, but easily overlooked ways to attempt to prevent data leakage, but overall speaking, they can be bypassed with some knowledge and are not the cleanest solutions.&lt;/p&gt;

&lt;p&gt;The first one would be setting proper permissions for the files that are said to have crucial data inside.&lt;/p&gt;

&lt;p&gt;When fusing/adding those files to the container itself, we can set only read permissions to them(either setting those on the data container/bucket that the data is being stored in or by changing the default unix permissions on a profile to 744 - read only to group and others) and disabling root access by switching its shell from /bin/bash or /bin/sh (or any other shell that permits user login) to /sbin/nologin, in the /etc/passwd file. That will definitely lower the chance to easily modify the data or execute some script on them that will spread the data to the internet.&lt;/p&gt;

&lt;p&gt;Another approach - that can and should be combined with previous one -  would be to configure tools like RStudio or Vertex AI to prevent file downloads in their UI - such a simple change may reduce risks when not as experienced users will use our environment. Unfortunately, since the users can execute arbitrary code in the IDE, we cannot stop here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Egress Prevention based on Google Cloud Platform&lt;/strong&gt;&lt;br&gt;
Google Cloud Platform, same as AWS or Azure - has a few out-of-the-box ways to secure a web interface, and hence prevent unauthorized persons from accessing it. We can archive that by using IAP (Identity-Aware Proxy), which is integrated with Google’s Cloud Identity. By having it connected to Google we can use Cloud Permissions systems consistently. When I first had to integrate security measures I assumed it would be a few simple firewall policy rules, but going through the implementation it is much more complex - adding IAP to non native environment makes the process quite complicated, the whole process will be described later. In addition to that “adding a few simple firewall rules” will not cover all edge cases so we need to actually add quite complex configuration, which also will not limit the user itself - quite interesting examples will be shown later.&lt;/p&gt;

&lt;p&gt;Going to the point, which is IAP since we need to add a layer of authentication before accessing the internet exposed endpoint. In the case of GCP there are several ways to achieve that. The easiest one is using AppEngine environments that are under the hood based on virtual machines but are restricted in some areas - one of those is not having access to persistent storage. Going straight to code, a simple App Engine with IAP can be created using Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "google_app_engine_application" "app" {  
  project = google_project.my_project.project_id  
 location_id = "us-central"  
  iap {  
   enabled = true  
   oauth2_client_id = var.oauth2_client_id  
   oauth2_client_secret = var.oauth2_client_secret  
  }  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, this only covers a simple AppEngine setup. What if our application is more complex, and we would like to have access to a more customisable environment to which persistent storage can be attached - in such case we will need to create everything that AppEngine is providing us out of the box, which is a point of connection to the environment itself. Starting from the top - to add IAP to the environment we need a FQDN that has SSL certificate attached to it, that means we need to add a dns entry. In addition to that load balancer is needed to separate the internet facing part and backend part of our configuration. With such a setup we have all the basic requirements fulfilled to enable IAP, the only part missing is an actual connection between IAP config and the environment itself.&lt;/p&gt;

&lt;p&gt;The last part in our case would be adding a network endpoint which is connected to designated vm and also attached to previously mentioned load balancer. There is also one hidden advantage of using a load balancer to access our environment - it serves as proxy and by default most of the cloud services provide out of the box traffic filtering and ddos attacks prevention.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "google_compute_address" "frontend-ip" {  
  name = "${var.name}-ipv4-address-frontend"  
}   

resource "google_dns_record_set" "a" {  
  name = "${var.dns_name}"  
  type = "A"  
  managed_zone = var.dns_zone  
 ttl = 180  
  rrdatas = [module.lb-http.external_ip]  
  project = var.dns_project  
}   

resource "google_compute_instance" "instance" {  
 name = var.name  
 zone = var.zone  
 machine_type = var.machine_type  
 allow_stopping_for_update = true  

 boot_disk {  
    initialize_params {  
      image = var.vm_image  
      type = "pd-standard"  
      size = 100  
  }  
  }  

  attached_disk {  
    source = google_compute_disk.persistent.name  
    device_name = google_compute_disk.persistent.name  
  }  

  network_interface {  
    network = var.network_name  
    subnetwork = var.region  
    access_config {  
      nat_ip = google_compute_address.static-ip.address  
      }  
  }  

  metadata = {  
   terraform = "true" 
   }  
  tags = ["ssh"]  
  service_account {  
    email = "${var.service_account_name}@${var.project_id}.iam.gserviceaccount.com"  
  }
  scheduling {  
    on_host_maintenance = "TERMINATE"  
  }  
}  

resource "google_compute_global_network_endpoint_group" "neg" {  
  name = "neg-${var.name}"  
  network_endpoint_type = "INTERNET_IP_PORT"  
  default_port = var.default_neg_port  
}  

resource "google_compute_global_network_endpoint" "endpoint" {  
 global_network_endpoint_group = google_compute_global_network_endpoint_group.neg.id  
 ip_address = google_compute_address.static-ip.address  
 port = google_compute_global_network_endpoint_group.neg.default_port  
}  

resource "google_iap_client" "oauth-client" {  
  display_name = "${var.name}-client"  
  brand = var.brand_name  
}  

resource "google_iap_web_iam_member" "access-role" {  
  member = "user:${var.emailid}"  
  role = "roles/iap.httpsResourceAccessor"  
}  

module "lb-http" {  
 project = var.project_id  
 source = "terraform-google-modules/lb-http/google//modules/serverless_negs"  
 name = var.name  
 ssl = true  
 create_ssl_certificate = true  
 private_key = var.private_key  
 certificate = var.certificate  
 address = google_compute_address.frontend-ip.address  

 backends = {  
    default = {  
      load_balancing_scheme = "EXTERNAL_MANAGED"  
  name = "backend-${var.name}"  
  enable_cdn = false  
  timeout_sec = var.lb_timeout  
  connection_draining_timeout_sec = var.lb_connection_drain  

  iap_config = {  
    enable = true  
    oauth2_client_id = google_iap_client.oauth-client.client_id  
    oauth2_client_secret = google_iap_client.oauth-client.secret  
  }  

      log_config = {  
        enable = false  
  }  

      groups = [  
        {  
          group = google_compute_global_network_endpoint_group.neg.id  
  }  
      ]  
    }  
  }  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We cannot trust that a wrong person will not know our IP or FQDN (fully qualified domain name). Having such a setup, which will prevent them from doing malicious actions, is an obvious thing to do to protect user environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VPC, Firewall rules and policies&lt;/strong&gt;&lt;br&gt;
Another option to make an instance more secure is creating a VPC network with subnets and defined firewall rules. With VPC in place, we can now proceed with adding rules and policies that will be our shield.&lt;/p&gt;

&lt;p&gt;Firewall rules in GCP are configurations that control the traffic to and from resources based on specified criteria, such as IP addresses, protocols, and ports. Firewall policies offer a higher level of control and standardization, enabling customization on FQDN and geolocalization. Furthermore, policies are coupled with a few out-of-the-box rules that should exclude threats that are widely known. The downside of the higher standardization is that it has to be used on a larger resource pool, like a project or even organization. On the other hand, each Firewall rule can be applied to a single resource like a VM.&lt;/p&gt;

&lt;p&gt;Using a simple terraform script, it can be done in seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "google_compute_network" "vpc" {
  project                 = google_project.project.project_id
  name                    = var.network_name
  auto_create_subnetworks = "false"
  routing_mode            = "GLOBAL"
}

//Subnet
resource "google_compute_subnetwork" "subnet" {
  depends_on    = [google_compute_network.vpc]
  project       = google_project.project.project_id
  name          = var.subnet_name
  ip_cidr_range = var.subnet_ip
  network       = var.network_name
  region        = var.region
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A firewall rules can be created by using terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
resource "google_compute_firewall" "sample_rule" {
 project       = google_project.project.project_id
 name          = var.rule_name
 network       = var.network_name
 source_ranges = ["0.0.0.0/0"]

 allow {
   protocol = "tcp"
   ports    = ["8888", "8787", "80"]
 }
 target_tags = ["http"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By creating a proper setup of Firewall rules we can allow only IPs that are associated with a specific user and therefore block anyone else from accessing them; furthermore, by doing that, most of the egress points will be blocked. Using policy rules would be a little bit more convenient since we do not always know what IP address the end user will be using and we would spare the need to adjust the rules whenever the IP changes. Creating a simple policy rule can be also accomplished by a Terraform script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "google_compute_firewall_policy_rule" "primary" {
  firewall_policy = google_compute_firewall_policy.default.name
  description     = "Resource created for Terraform testing"
  priority        = 9000
  enable_logging  = true
  action          = "allow"
  direction       = "EGRESS"
  disabled        = false
  match {
    layer4_configs {
      ip_protocol = "tcp"
      ports = [8080]
    }
    dest_ip_ranges = ["0.0.0.0/0"]
    dest_fqdns = []
    dest_region_codes = ["US"]
    dest_threat_intelligences = ["iplist-known-malicious-ips"]
    src_address_groups = []
    dest_address_groups = [google_network_security_address_group.basic_global_networksecurity_address_group.id]
  }
  target_service_accounts = ["my@service-account.com"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Limiting Outbound Connections&lt;/strong&gt;&lt;br&gt;
Theoretically speaking we should be able to block outbound connections on the firewall level and allow the application itself to respond to incoming ones, which in our case would be users connecting to the Web UI. In most cases that is the course of actions that should be pursued, but in case anyone would like to use Vertex AI Workbenches it is not archivable easy way. Vertex AI Workbenches are heavily reliant on GCP itself so they need to have access to making outbound connections, to be precise we need to enable access to both private and restricted GCP IP pools or the environment will try to boot up infinitely.&lt;/p&gt;

&lt;p&gt;In addition to that when the user is trying to connect the environment itself is adding custom elements to the UI so for that brief moment it needs to connect to the internet, but all is not lost yet - if the user is already connected there is no more need for such requests so we can modify our firewall policies “on the fly”.&lt;/p&gt;

&lt;p&gt;The flow is quite simple: an environment is being created with an IP open for the user, the user itself starts establishing connection to the Vertex AI workbench, then the moment connection succeeds we initiate a process to modify our policy rules to block that point of connection. Since the user is already in he/she will be able to use that workbench but we will be protected from other connections. Having such a setup is hard to make but definitely would serve its purpose.&lt;/p&gt;

&lt;p&gt;On the other hand combining a few policies - including those that exclude typical egress points like github, pastebin, pip and other data storage services - can create a solid shield for data which is much more maintainable. Excluding pip is a bit troublesome since the user will not be able to install packages but that can be addressed by pre installing those libraries on environment start or providing a private pip cache server that will be allowed to connect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
In today's data-driven world, safeguarding data egress is crucial for organizations. Implementing proper permissions, leveraging built-in egress prevention tools, and using advanced configurations like load balancers with IAP and VPC networks are essential steps in securing data environments. These measures help prevent accidental leaks, data reidentification, and malicious activities, but are not definitely foul proof and those accidents can still occur, so keep in mind that we also should have some steps to proceed prepared for such scenarios. On the other hand, keep in mind it is always a tradeoff between security and usability. The more secure the environment, the less the user can do and experiment on. Having a proper balance between those two can create a user-friendly space.&lt;/p&gt;

&lt;p&gt;Disclaimer: This article was originally written by Upside Labs.&lt;/p&gt;

</description>
      <category>dataegress</category>
      <category>ai</category>
      <category>database</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
