<![CDATA[Yashints | Blog]]>https://yashints.devGatsbyJSFri, 07 Jun 2024 03:42:12 GMT<![CDATA[🖥️ Use local AI to supercharge your coding skills]]>https://yashints.dev/blog/2024/06/07/ollama-continuehttps://yashints.dev/blog/2024/06/07/ollama-continueFri, 07 Jun 2024 00:00:00 GMT<p>Are you a developer who wants to use AI in their day to day coding but don’t have access to tools like <a href="https://github.com/features/copilot" target="_blank" rel="nofollow noopener noreferrer">GitHub Copilot</a>, <a href="https://github.blog/2024-04-29-github-copilot-workspace/" target="_blank" rel="nofollow noopener noreferrer">GitHub Copilot Workspace</a> or Cloud based services like <a href="https://azure.microsoft.com/en-au/products/ai-services/openai-service" target="_blank" rel="nofollow noopener noreferrer">Azure OpenAI</a>? If yes, read on and you’re going to have super powers by the end of this post.</p> <!--more--> <h2 id="why-bother" style="position:relative;"><a href="#why-bother" aria-label="why bother permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why bother?</h2> <p>If you’re wondering what is all the fuss and why should I bother, I have to tell you AI is moving like a speed train and if you don’t jump in, you’ll be left behind and it would be very hard to catch on later. AI can help you enhance your coding skills, increase your productivity, and help you learn a great deal more than you do in your job.</p> <p>Here are some reasons why any developer needs to use AI:</p> <ul> <li><strong>Automated code generation</strong>: AI can generate code and save you time and effort. You can get suggestions with snippets of code and even get the rest of your code completed as soon as you have some of it written. This can speed up your coding and development process by a great deal.</li> <li><strong>Code review</strong>: AI can help to review code and identify potential issues, bugs, security vulnerabilities and code smells before they even become problems. This can improve the overall quality of your code and products and reduce time spent fixing bugs after go live.</li> <li><strong>Personalised learning</strong>: AI can provide personalised learning experience for you and help improve your skills and knowledge in a way which is tailored to your needs and learning style.</li> <li><strong>Natural language (NLP)</strong>: In addition to all of the previous points, you get to use your natural language to interact with AI using prompts, voice etc which makes the whole process even faster.</li> </ul> <h2 id="setup---lets-get-cracking" style="position:relative;"><a href="#setup---lets-get-cracking" aria-label="setup lets get cracking permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setup - let’s get cracking</h2> <p>Enough talk, let’s get into the action. You will need these before you can leverage AI in your development environment:</p> <ul> <li><a href="https://ollama.com/" target="_blank" rel="nofollow noopener noreferrer">Ollama</a> which is an open source project allowing you to interact with LLMs locally. If you don’t have it setup, <a href="/blog/2024/05/28/local-llms/">use my earlier post</a> to get setup. We also need to have a model installed which supports code generation.</li> <li><a href="https://code.visualstudio.com/" target="_blank" rel="nofollow noopener noreferrer">VS Code</a> which is also an open source code editor which has billions of users globally. <em>You can use <a href="https://www.jetbrains.com/" target="_blank" rel="nofollow noopener noreferrer">JetBrains</a> if you are using that already BTW</em>.</li> <li><a href="https://www.continue.dev/" target="_blank" rel="nofollow noopener noreferrer">Continue</a> which is an extension which allows you to leverage LLMs in your code editors using cloud or local hosted models.</li> </ul> <h3 id="install-the-code-generation-model" style="position:relative;"><a href="#install-the-code-generation-model" aria-label="install the code generation model permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Install the code generation model</h3> <p>Go into the environment which you have Ollama installed and install the <code class="language-text">starcoder</code> model. You can get the 3b parameter version or if you’re adventurous like me, get the 7b version which is roughly 4GB.</p> <div class="gatsby-code-button-container" data-toaster-id="19696139080787380000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`ollama pull starcoder2:7b`, `19696139080787380000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">ollama pull starcoder2:7b</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>And you should see the model being pulled and installed.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/f80157b18b008a25f3bf964a2fb98e78/ab98c/pullmodel.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 20.740740740740744%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA+klEQVR42j3P7UuDUBQGcD/Worex0pmZXl/urjZd6qblxojqSxCtFdXofwgGo1W46D9/Ol5iH36cBw48nKPEaYEo6YMHEaKzAeI0RxCl6FL2RATGT2F5Ag4PYfsB2qZDGEzGYblird7VlN5giGJ4iXJ8DVfEVJbhfb5A9fOL5VdFVvggy+/Vf64wX3wi7pfQ7Q5aRwwHhkNcSQmTc8TZBZJiBDfowWACN7cT3D28SPePr2uTpxmmz2+UZ/C7GRUKqKYP7aTG5VTy0RXycizfrDE/hGrY0Om1pnqMxp6GjZ1DbO6qaOxr2Grq2G4ZdJlDZR40i6NtdaS69A9FZIBrryy18wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Pulling down the startcode model in Ollama" title="" src="/static/f80157b18b008a25f3bf964a2fb98e78/302a4/pullmodel.png" srcset="/static/f80157b18b008a25f3bf964a2fb98e78/01bf6/pullmodel.png 270w, /static/f80157b18b008a25f3bf964a2fb98e78/07484/pullmodel.png 540w, /static/f80157b18b008a25f3bf964a2fb98e78/302a4/pullmodel.png 1080w, /static/f80157b18b008a25f3bf964a2fb98e78/0d292/pullmodel.png 1620w, /static/f80157b18b008a25f3bf964a2fb98e78/b3608/pullmodel.png 2160w, /static/f80157b18b008a25f3bf964a2fb98e78/ab98c/pullmodel.png 2356w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h3 id="install-the-extension" style="position:relative;"><a href="#install-the-extension" aria-label="install the extension permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Install the extension</h3> <p>First you need to install <a href="https://marketplace.visualstudio.com/items?itemName=Continue.continue" target="_blank" rel="nofollow noopener noreferrer">Continue extension</a> in VS Code. You can click on the link from here or go into your VS Code extensions and get it installed.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/2ec0daeea40fd60badd27bb93e4b4058/6114f/vscodeext.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 31.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABbElEQVR42k2RzUvjQBiH+3eYNMlkkjaJaTr90K2KSlUW9qiC4HXvHhT/Hi/Cssv+AQoLWvqBsAseBC8e9KQHK5hLLUrhcRL82IGHl5dhnnfmNwUhfYIoRvplpFfC07jSo9GcIUkShBBI3ZfLek8KXNfFtm0Mw/igWCziOE5eCwvtb6xu7VBpb6O+fkeGtfzg0dExu3v7+EGMUnVUtUpdxcSxHi4llmXl4kyU1fe+0GoqVlbaJLUmanaZUhjnkzbXN5hfWsOWEaYxhWmaGBrzjUyQybL6LsuFSVJlOgqRwsELKjiuh1fyOfhxSOf0JKfb7dHv9zU9Op0Og8GAn79/6dtWCMNQx+R9CucW2zRb89hWkVKkEK7ONAj4d3NJtsZPY25v7xgOH7i/H/Ly8sxkAhdX11RrNZ27n+f8IazPfEE1ZnFsC+FH2EJS9kPOzi8YjUekaaplw5xMmKaPPI2e+TP4S5jUsR39Sf89+RUAsOoevZeb5QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Installing continue extension in VS Code" title="" src="/static/2ec0daeea40fd60badd27bb93e4b4058/302a4/vscodeext.png" srcset="/static/2ec0daeea40fd60badd27bb93e4b4058/01bf6/vscodeext.png 270w, /static/2ec0daeea40fd60badd27bb93e4b4058/07484/vscodeext.png 540w, /static/2ec0daeea40fd60badd27bb93e4b4058/302a4/vscodeext.png 1080w, /static/2ec0daeea40fd60badd27bb93e4b4058/0d292/vscodeext.png 1620w, /static/2ec0daeea40fd60badd27bb93e4b4058/b3608/vscodeext.png 2160w, /static/2ec0daeea40fd60badd27bb93e4b4058/6114f/vscodeext.png 3823w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h3 id="configuring-the-extension" style="position:relative;"><a href="#configuring-the-extension" aria-label="configuring the extension permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Configuring the extension</h3> <p>Once you have both of those installed, you can go ahead and configure your extension to use your local Ollama instance. Open the <code class="language-text">config.json</code> settings for Continue using <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd>, then typing <code class="language-text">> continue: open config.json</code>. Keep in mind that Continue supports a <a href="https://docs.continue.dev/setup/select-provider" target="_blank" rel="nofollow noopener noreferrer">whole host of providers</a> other than local ones. Go ahead and add the models you want to the config file. Do not forget to change the IP address to match your own instance if it is not available on <code class="language-text">localhost</code>.</p> <div class="gatsby-code-button-container" data-toaster-id="6950079955447253000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`{ &quot;models&quot;: [ { &quot;title&quot;: &quot;Llama 3&quot;, &quot;provider&quot;: &quot;ollama&quot;, &quot;model&quot;: &quot;llama3&quot;, &quot;apiBase&quot;: &quot;http://localhost:11434/&quot; }, { &quot;title&quot;: &quot;Starcoder 7b&quot;, &quot;provider&quot;: &quot;ollama&quot;, &quot;model&quot;: &quot;starcoder2:7b&quot;, &quot;apiBase&quot;: &quot;http://localhost:11434/&quot; } ], &quot;customCommands&quot;: [ { &quot;name&quot;: &quot;test&quot;, &quot;prompt&quot;: &quot;{{{ input }}}\n\nWrite a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated. Give the tests just as chat output, don't edit any file.&quot;, &quot;description&quot;: &quot;Write unit tests for highlighted code&quot; } ], &quot;tabAutocompleteModel&quot;: { &quot;title&quot;: &quot;Starcoder 7b&quot;, &quot;provider&quot;: &quot;ollama&quot;, &quot;model&quot;: &quot;starcoder2:7b&quot;, &quot;apiBase&quot;: &quot;http://localhost:11434/&quot; }, &quot;allowAnonymousTelemetry&quot;: true, &quot;embeddingsProvider&quot;: { &quot;provider&quot;: &quot;transformers.js&quot; } }`, `6950079955447253000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"models"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"title"</span><span class="token operator">:</span> <span class="token string">"Llama 3"</span><span class="token punctuation">,</span> <span class="token property">"provider"</span><span class="token operator">:</span> <span class="token string">"ollama"</span><span class="token punctuation">,</span> <span class="token property">"model"</span><span class="token operator">:</span> <span class="token string">"llama3"</span><span class="token punctuation">,</span> <span class="token property">"apiBase"</span><span class="token operator">:</span> <span class="token string">"http://localhost:11434/"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"title"</span><span class="token operator">:</span> <span class="token string">"Starcoder 7b"</span><span class="token punctuation">,</span> <span class="token property">"provider"</span><span class="token operator">:</span> <span class="token string">"ollama"</span><span class="token punctuation">,</span> <span class="token property">"model"</span><span class="token operator">:</span> <span class="token string">"starcoder2:7b"</span><span class="token punctuation">,</span> <span class="token property">"apiBase"</span><span class="token operator">:</span> <span class="token string">"http://localhost:11434/"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"customCommands"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"test"</span><span class="token punctuation">,</span> <span class="token property">"prompt"</span><span class="token operator">:</span> <span class="token string">"{{{ input }}}\n\nWrite a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated. Give the tests just as chat output, don't edit any file."</span><span class="token punctuation">,</span> <span class="token property">"description"</span><span class="token operator">:</span> <span class="token string">"Write unit tests for highlighted code"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"tabAutocompleteModel"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"title"</span><span class="token operator">:</span> <span class="token string">"Starcoder 7b"</span><span class="token punctuation">,</span> <span class="token property">"provider"</span><span class="token operator">:</span> <span class="token string">"ollama"</span><span class="token punctuation">,</span> <span class="token property">"model"</span><span class="token operator">:</span> <span class="token string">"starcoder2:7b"</span><span class="token punctuation">,</span> <span class="token property">"apiBase"</span><span class="token operator">:</span> <span class="token string">"http://localhost:11434/"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"allowAnonymousTelemetry"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token property">"embeddingsProvider"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"provider"</span><span class="token operator">:</span> <span class="token string">"transformers.js"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="testing" style="position:relative;"><a href="#testing" aria-label="testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing</h2> <p>Let’s test our setup, go ahead and open a file in which you have any code written. Select a code block and press <kbd>Ctrl</kbd> + <kbd>L</kbd> which opens the chat window, type <code class="language-text">Explain this code to me in a few sentences</code> and press enter. You should see the explanation getting generated in the chat window:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/90a091790edb4e4bfc9a3317d27a018d/38abd/explain.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 73.7037037037037%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACHElEQVR42p1UWa6jMBDkFtnY4hVjbCAhb6KR5v7Hqqk2eVE0X6P3UQLs7uqlKql6ZaC1Qtt2aJoGSilYa6GVhup7XM5nNHXN+/YNiXHOoef95/mZsZUcDmEsJNfrleQaxlgmeIQQMUwZfhzRdT3RoSUudYPL5YKahaQJQds2hbRS7GRdV6SUSlUhlg4E2gX4dMc4z5iWGcvGuCUheEs4aKPQs4m9066gknGXZXmTeu9fXRoobeHjjHT7wsiYtGas84jHMuFG5Byx5AlxHOBIrtUVlbED5pzweGxvYiHTeicclhXz7z+Y1lu5+7U9EGMssSlyAqPhrSlQQqiZvG0b7vd7SRi5LxlX9nglqWF1HSzs4GF8QK8szDCy88yVeJ6PEI76JVwle3s+n9gej0Iqlb0fyi6tk5ETFt7FG0dfE7Z7ZkxEThw1kpjCBYoq4jmuqxJlnSRzl4rvstiaCnonS6d9DLtgAUVyNzikKWAMns4IGAaec98irNa7mFVgYpbgvoPp6LFuJxRhFEfePbbbQuxR1/JsC76tsltnt1Al5rViEdkbSTS7FIPu3hLP1WU/7+fLf/XH9/F4fKOSCpZdLjnjNkUqFwvh4XAokKDP9+/v4+vs87wQSnKgsjeKsdCHEwkbFvk38H9RdRxtoiUynT9yZCvOF8IfkBVC+eEbkqQwILPTidLX7Pr4U8L+qor07uV0+RMQQU6n04/wFxOIojU4QUrfAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Local LLM being used in VS Code using the Continue extension" title="" src="/static/90a091790edb4e4bfc9a3317d27a018d/302a4/explain.png" srcset="/static/90a091790edb4e4bfc9a3317d27a018d/01bf6/explain.png 270w, /static/90a091790edb4e4bfc9a3317d27a018d/07484/explain.png 540w, /static/90a091790edb4e4bfc9a3317d27a018d/302a4/explain.png 1080w, /static/90a091790edb4e4bfc9a3317d27a018d/0d292/explain.png 1620w, /static/90a091790edb4e4bfc9a3317d27a018d/b3608/explain.png 2160w, /static/90a091790edb4e4bfc9a3317d27a018d/38abd/explain.png 2766w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="using-tab-for-auto-completion" style="position:relative;"><a href="#using-tab-for-auto-completion" aria-label="using tab for auto completion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Using tab for auto completion</h2> <p>You can use tab to complete code in your code file. Create a new file called <code class="language-text">index.js</code>, enter below comments:</p> <div class="gatsby-code-button-container" data-toaster-id="14147949573603547000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`// a function which calculates fibonacci sequence // and returns the nth number in the sequence`, `14147949573603547000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// a function which calculates fibonacci sequence</span> <span class="token comment">// and returns the nth number in the sequence</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>Go to the next line and wait until your see the function name getting generated, if it didn’t type the function name and wait, it should generate the function and show you in highlighted grey text:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/944e3c4cc7a9ee991cfb44aef5fb39dc/8ae78/codecompletion.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 35.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAA40lEQVR42o1R2W7DIBDkPxpzGLDNYRtI4tZR+/+fNQUalLSylD4Me2p2hyVfnyv8GmHWDZPz2PeA/SPgelnwvnmkMCLFEYJzMMZegsRoEc8p44w1zNiuM257ynbJRBPCouGsAhccnL8GYYxWxxoD5xy0HtH3Ct2J4i3j1FF0Gc9bPBPUmD22JyUppYT3Htba6nPOQCmtOJLVakc9pDxCCLhMWEi11nXTNsC5nyFSKYzjcB8+w+ceYyxstlL2ULk+DANICUpTI25Sil/B7zn2kNhqv2S3P7ykKa/N/nXBQ/l/rvwNPdDFs73BBzQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Continue generating code from comment" title="" src="/static/944e3c4cc7a9ee991cfb44aef5fb39dc/302a4/codecompletion.png" srcset="/static/944e3c4cc7a9ee991cfb44aef5fb39dc/01bf6/codecompletion.png 270w, /static/944e3c4cc7a9ee991cfb44aef5fb39dc/07484/codecompletion.png 540w, /static/944e3c4cc7a9ee991cfb44aef5fb39dc/302a4/codecompletion.png 1080w, /static/944e3c4cc7a9ee991cfb44aef5fb39dc/8ae78/codecompletion.png 1096w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Press <kbd>Tab</kbd> and the code should be persisted.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/9478b770b792262cc21c73a5266cd384/cd536/compleetedcode.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 34.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAA+0lEQVR42pVR7W6DMAzkQdZ8QTCkQJNSaAntD/b+73QzaZnGpknbj5OjO/lsXzJqB1TdgPoUQXWD+2PAfL9gHAPi5NH3Dq6SkPJvyKg6IrBpw+hCgA8tbmGEoxrWGhSFgTH/MNRagQrCzU+4+isbezYiiDeBw0FAML43CfGV2+uZzS2OJZ/aP3B2ZzavoHjI2qSUZGxv9WxiTqvfdd5QJ5MYZgzr6a7Gstwwxwti7OHHGUVJcG0Hci3apsb7MrHeY5peemFZP6U/yIjPs6ZME7Q2qeb5ml2e6pPTid+w6cawbsxOyyrSEFJ9ZrCtLsQ+N/WCVOpHplvPGsEH8F7IN0UNhFEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Getting autocomplete on tab" title="" src="/static/9478b770b792262cc21c73a5266cd384/302a4/compleetedcode.png" srcset="/static/9478b770b792262cc21c73a5266cd384/01bf6/compleetedcode.png 270w, /static/9478b770b792262cc21c73a5266cd384/07484/compleetedcode.png 540w, /static/9478b770b792262cc21c73a5266cd384/302a4/compleetedcode.png 1080w, /static/9478b770b792262cc21c73a5266cd384/cd536/compleetedcode.png 1114w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="refactor-code" style="position:relative;"><a href="#refactor-code" aria-label="refactor code permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Refactor code</h2> <p>When you’re writing code, you could ask AI to refactor the code for you. Go ahead and clear the content of the file and replace it with below:</p> <div class="gatsby-code-button-container" data-toaster-id="77809810022103630000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`function calculateTotal(items) { let total = 0; for (let i = 0; i < items.length; i++) { total += items[i]; } return total; }`, `77809810022103630000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">calculateTotal</span><span class="token punctuation">(</span><span class="token parameter">items</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> total <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> items<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> total <span class="token operator">+=</span> items<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> total<span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Now press <kbd>Ctrl</kbd> + <kbd>I</kbd> and type refactor this code in the prompt and press <kbd>Enter</kbd>. Wait for the model to do its thing and you should see the result which you can accept or reject:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/e7877870f4f16f7ba767c9e1abc90f81/082c8/refactor.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 27.40740740740741%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAwklEQVR42o3O206DUBCFYV5ECvsEe1eIRNNCBam2VAgl8f1f5ncwva3pxZeszExWJlIqpSm2tOUq0BaeTvIhOJrcUmeaOjc03vJmEl715q5KJ0QqTRmfHT8vnqXMuRaO69YwB82UJYw2fsgkTiZeCxVfwTJJybdXXNyGQZztk4gfNoh+LdTG8HncM44ty3JgmRumuWO47Oj7SnYVp/OO40dJ+x7u6tpAvfdERgozr8nkyyyYG/vHSXa3vM6d3P3H5opfYo2Z7MBe/BUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Code refactoring using local LLMs" title="" src="/static/e7877870f4f16f7ba767c9e1abc90f81/302a4/refactor.png" srcset="/static/e7877870f4f16f7ba767c9e1abc90f81/01bf6/refactor.png 270w, /static/e7877870f4f16f7ba767c9e1abc90f81/07484/refactor.png 540w, /static/e7877870f4f16f7ba767c9e1abc90f81/302a4/refactor.png 1080w, /static/e7877870f4f16f7ba767c9e1abc90f81/0d292/refactor.png 1620w, /static/e7877870f4f16f7ba767c9e1abc90f81/b3608/refactor.png 2160w, /static/e7877870f4f16f7ba767c9e1abc90f81/082c8/refactor.png 2734w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="ask-questions-about-your-codebase" style="position:relative;"><a href="#ask-questions-about-your-codebase" aria-label="ask questions about your codebase permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Ask questions about your codebase</h2> <p>You can also ask questions about your codebase by pressing <kbd>Ctrl</kbd> + <kbd>L</kbd> and typing <code class="language-text">@codebase</code> and then your question.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 829px; " > <a class="gatsby-resp-image-link" href="/static/6eb6420cf2603c3e0a390bcdd9c65f8b/9d76a/codebase.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 192.962962962963%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAnCAYAAAAPZ2gOAAAACXBIWXMAABYlAAAWJQFJUiTwAAAE1UlEQVR42oWW2VbjSAyG/Q4QCCGbs4fYsePsCWFNoLnkDbjlnsPra/Qpkcfd05y50KkqVZXWX6oKepOd9JOl9Ltt6XS6MhqNdOzIcDjM575ut9vS7Xal1+sZwb+5uTFijzFo3UwlHCTSabckbLXsUKPRkH6/b0JYt5Q/GAwkDENbu2DWCGbebDbtTNCoV2XQ68rF5aUJHMdjScZjqVarcnFxIeVy2ehS930OsReGTVM8n89MWK1WkwANLLhQqVwbE7q85FLJ+E4IKc4RjGI8qOud6+trCWBeXV3ZIk1TmU6nRlk2kTiO8xhBxLS4jqLIeFgJEaoA32Ei+OPjQ76/v+Xr60s+Pz/l7e1NXl5e5Pn5WQ6Hg7y+vtoIwXfabrdqQGaxDSqVipRKJTN9t9vZ5SRJcppMJmZ5kX7i4XpApiDihqtsoA06uv7feZHn/NlsdhToeCKOrRNsMB1yZUUiRD8RXgaetWJyIEJxfn5uREiK82K2i8SdAFCSMbeQbDFiOcBmjdVAy4Fer9dNKXecMMgE4gamukAyjrtcRBk8iLmvi9YXwW8CSQb4QSDusEHWPMME3DPOnL35fC7L5VI2m41hFYNyl3GFQ66Fzfv7+xx3wOjx8VH2+72Rz29vby2z3CUEuUCfYCFzBN7d3cnT05MBdrVa2RwFCGHv4eFBFotFDpnfBBJoXHML2QTgWIEgiDXuoQCh6/XaiDPwfhPo+EIYMcRSjx+xglAKzkjIv3gMNfZNnWuSNOO5QNJfO3UKb6JFEIfNhrS0TXU6mvl2aOu2jr2uFkCLcw1LKtnGmMC7MdK99Ah0RnymmuFsIeNsJd1hLOOpZlXnUbqQZLqWdL41XhTF1hutH3qzZOH1icDZJJN5mkg8WUm8+iXR8iDj9atEi73E618SL/cyWhx0/irZbGFN1mqZKsEqsmtFr4K6WiWJgng10r6nrmaTVAZ9rahBX6KbgcTRUNIklqHyWE/Vk1rtmJgA/719A9Kxtv+2ajpXq0t0Zu3cJZ3zRDDa/ELnpeMIUTEIs46NZV563hR8hO/N4ideMakmEFjgMnGkpbMGtICXcBAGygzcAWr2wSK4JNYAn8RyBg8DLEMY0r2vARngQzh8DTkOvWGwxzkf2csbLNmmVRFDNP0fcY7zVAkCUc7cgI1k4gMzNCtaf+3WRbKnUwVwz8li6G0fRqxaI4XLNhpJqpb3T50Ia4gn3hBn1pQn3YaRNXwrPVrV+/u7xRFXZgBbDzFPNVkcHusagbFegj86vdEkMzrxcJuwBW4qRJVwkItJkkrU78lcwR3rmNB0lfBioKXKWUoWYYwoNgv97UAgwlyzuaWCZ1p+2cnKTNeLcSzLOJI0jo7u4okq8pfTmoN/I9AKzsAY74e9K6qwraNBSZMw1A4z4B1XgDsfo7DOXPZK8U+Pf9nKvDGFJ9RGpbPTSMzhOeXtCxfJFrWIlU4OVEeBE9b4R8BfRz9jOCxfVUwY0skYwnGbMvPngVBQjpQca0JCyRFD+AjkHsYF292DvnJ3KrCSl56jvrh2XtELiFAx0iDsK7La7PSh2ZiVuONALdKfPy3/bdH2UOKKrGOXtJ+dnZ3ZwmNiJXh6U3AHzf73+ZOK32RLSrV6BDVaiKET8SCGWOM/g+LlvxEC/wHidWhQmGvB8wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="using context to ask question about the codebase" title="" src="/static/6eb6420cf2603c3e0a390bcdd9c65f8b/9d76a/codebase.png" srcset="/static/6eb6420cf2603c3e0a390bcdd9c65f8b/01bf6/codebase.png 270w, /static/6eb6420cf2603c3e0a390bcdd9c65f8b/07484/codebase.png 540w, /static/6eb6420cf2603c3e0a390bcdd9c65f8b/9d76a/codebase.png 829w" sizes="(max-width: 829px) 100vw, 829px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="use-documentation-as-context-for-your-questions" style="position:relative;"><a href="#use-documentation-as-context-for-your-questions" aria-label="use documentation as context for your questions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Use documentation as context for your questions</h2> <p>You can use documentation when interacting with AI which adds more accuracy to the response. The team have done a fantastic job and have pretty much any language or framework you can think of linked. You can use the <code class="language-text">@docs</code> and then select your preferred documentation, or just simply type the doc name if you know it.</p> <p>Let’s open the chat window using <kbd>Ctrl</kbd> + <kbd>L</kbd>, then type <code class="language-text">@react</code> and then <code class="language-text">How do I update the page based on a parameter?</code> then press <kbd>Enter</kbd>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 784px; " > <a class="gatsby-resp-image-link" href="/static/7c25b147c7111cd502e4fd4fede99b5c/4971b/docs.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 135.92592592592595%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAAACXBIWXMAABYlAAAWJQFJUiTwAAADYUlEQVR42oWVyU7jQBCG/QwsWQjZnDibY8fZE7IAAiQkDtwQF8Qj8P6nmvqKlGXQjObwu7qrq/+urdtBs9mUdrst1WpVWq2W9Ho9Q73RMN3V1dU/cX19Lc1mQ8bjWDG2fQGD0WgkpVJJwrAjSZIqEmko4eXlpZTL5b8Ce0ixi+ORDIdD6YShBLVaTSqVii3iUelEwukYg3q9bnPkb7CffUjmASF3u11TvL+/y9fXl3x8fMjt7a08PDzI3d2d3N/fy8vLi83B4+OjPD095UB3PBxkPp9LQP4gxYPX11f5/PyUt7c3eX5+loMarddrubm5kePxKNvt1oB+v9/LbrczbDYbw3Q6lQB3CRFS3+CGGGRZZphMJj/GPneJ7Ww2+y5Kv9+3kBnHcWxFAhQnTdP/wu2QAa3ihcHLUCuFzvXFNiGaYisV5z4OvDquKILWoHWQFxcX+dzB3HU4BAJi9yrjMv1EgxIyqSANg8HAQkICbIohkybqYIRUlwleLpdLA4mmBcBisbBNq9XK5p58iseYFOFMToh3AGKIfINXzz3AU8bofIwNe4nEHQsIjzBQeFsQOpuclAPwDsmBxepiQ1SdTsfyGcBKASAkPAebfaPnEkBCfllHkk9IccwIWcAIUgw8FEjOz8+tulQRWcTZ2VmudzsLGVe9bfwhQNKT/jj8hvcptkh0XGGk9SHwopAzckLOKIZfK9ZIhReNdSLz60cXYB/wIQ+U3MP1yroxc7+ipKWYS3LHIVEUWdiBP5Z4SG9BgAd4iUccBopechivEICYq5ffFD+tWq3kZGCop1sf6hoESyXiEPOa6qt+TugK8pc3tl8rTsnwQjFUHQmu8SLzWtc16XojGhRKwf8GNCmIztlLlEbIJw9ZyRbkUfMS6c9n0G7JKGxLTzcxj3jdG98y1INAX0k7QLsF4oCXN9a/VkVd3hCGhrpJxrJOE9lnE9kptnrIYZqZbqG220kq09FQ56nZjrltmmd7vug378Mk1aRrnlLypOhGPUlpJc1TNtM2Gicy5YbwCCvmahtp5cungljINKa9Nhry6vTaUADagDB6KrNTdSkI/YZkPj/dMr+qRuj3EQ95ORhTVSpefCBcsgYJje09Chl6I/THgbD5c7EIkfchIAr/L/utKs6tI06/kT/9TBQXoJ8adgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Using documentation when interacting with AI" title="" src="/static/7c25b147c7111cd502e4fd4fede99b5c/4971b/docs.png" srcset="/static/7c25b147c7111cd502e4fd4fede99b5c/01bf6/docs.png 270w, /static/7c25b147c7111cd502e4fd4fede99b5c/07484/docs.png 540w, /static/7c25b147c7111cd502e4fd4fede99b5c/4971b/docs.png 784w" sizes="(max-width: 784px) 100vw, 784px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="perform-actions-with-slash" style="position:relative;"><a href="#perform-actions-with-slash" aria-label="perform actions with slash permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Perform actions with slash</h2> <p>You can kick off an action using the slash in the chat window, this could be edit, comment, share, commit, test (which I had in the config and shows you how to do custom ones) or any predefined custom command.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 703px; " > <a class="gatsby-resp-image-link" href="/static/b00b9ec1b0c6178195d8bc356933be1e/242e2/slash.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 77.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB9klEQVR42qVUXW8SURDlN0jLfl8WlkB3Yb93KWzpUiVEoVCMaaqJNqkf0fTF+KA+1EQb+8OPM9diTB9U8OHkzsydOXNmZ3crQgjYto16vQ7DMKBp2l+hqiqEZcEidDodNBoNCa6vxFGCJE2RJgkazSZqtZos+BM4p9VqwXFamE4n2HP30O160HUdFUVRZMK/EP0OrlvXrmNSYa/XI/buf8N1XZimicpwOEQcx8iyDCmPvgW4Nooi+UwrnueBVXIH7sT+JuBa3/elLQnDMEQQBBiPx8jzXMrmra83twkkIUtlQpae0Ka5AXflhCZtfRNIQiZjAlbHz5OV9vt9qXIrQp6fVRVFgbIs5YvKF47jbEfIIzN4U4PBAO12e2OiO4QxbSkgsiEyGtuiT7HZdLbALWEah0gjH2kcYFTs46gsYAsDwtRgWwbqlv4Ld31hUkyQLU8TQvBSDhboFifIJ6eIj57ASR/BLxaIyhU6+zP0yPYPfsIbzil3gXC0REAID0/gDuZ0t0Qnm8C0BP0c5peIF+8xPf+C/PEHVJNnKE4/4vj1NUZnn3D/+RUeXnzF7OU3GX/w4gqrdzdYvb3B8s13HD79jNmra2THl9ANUqgZJlTdxL1dFbuKDo2C1ZpGvkK+hh2yq3RXranYYf82xuc6h/P5NA0DPwD0uNZ9K6j/wQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Slash actions in using continued" title="" src="/static/b00b9ec1b0c6178195d8bc356933be1e/242e2/slash.png" srcset="/static/b00b9ec1b0c6178195d8bc356933be1e/01bf6/slash.png 270w, /static/b00b9ec1b0c6178195d8bc356933be1e/07484/slash.png 540w, /static/b00b9ec1b0c6178195d8bc356933be1e/242e2/slash.png 703w" sizes="(max-width: 703px) 100vw, 703px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I will use the test command, select the code in your file and press <kbd>Ctrl</kbd> + <kbd>I</kbd>, then <code class="language-text">/test</code> and finally <kbd>Enter</kbd>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/03718092b6ae83d66dbc3f9bb03f008a/5df5d/test.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB70lEQVR42o2T2ZKaUBCGeYmMC7sLCWcTBEsWRcEAOhljKsm8/6P86YMzk6kkF7n46L+76IU+B4NxhnWyQRAwuI4Dy5xiMnrA6OEDxq+MRhiPx5hMp5gSk8mEuOs3Jve4EQQ+lBLgYg2mYogogdjswNKS2IOvtwi5QMg+wvdMONTUdWyyhK2x7lbHXReG63pYLudIkhWSqMQqzpD3N+y7R5weL8jrM9LigDQvsN5mCFiEIJQIhaJGEqs0B1cr8JDB8zwY+rFYLDDzqJN172pbGhOefY/ZtkO+A8+f0xQ+HBrCpTyd6/kz0j70YDblDgV934dQEut0DRVJRDF1JssFg1xxSMWhtJXhb02W8U8QQ4wNvtZU0B8mLE4Vum8tup9XtE8lukuMqlEoKoHyqAaKoyQrX3yJ4vDqy7d3hgln8zkOdYn+VqP52uHy/IT2esDxesKJilc1R7YXyKu/yf7whwmXesKdQH0psa9jHM8FDl2G7Y69KyQHvd3xAa3/heHTUoMgoFOt0FwbtN97+uwL6luLllZw/tGiv+7QfslQfY7Q9wku5xT7JkLTxuj6FGV9X4fG8GczLKmgogNJyxybPEa2SxHlOaJCX5WIDiYAk8sBoYIBrfkL7B2Gvox6j/oPMacac8AirTFNC5a+Nv/JL9Q8T4bUI1i1AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Using commands to perform an action" title="" src="/static/03718092b6ae83d66dbc3f9bb03f008a/302a4/test.png" srcset="/static/03718092b6ae83d66dbc3f9bb03f008a/01bf6/test.png 270w, /static/03718092b6ae83d66dbc3f9bb03f008a/07484/test.png 540w, /static/03718092b6ae83d66dbc3f9bb03f008a/302a4/test.png 1080w, /static/03718092b6ae83d66dbc3f9bb03f008a/5df5d/test.png 1572w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="use-files-as-context" style="position:relative;"><a href="#use-files-as-context" aria-label="use files as context permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Use files as context</h2> <p>You can use files in your context by using the <code class="language-text">@files</code> tag. Simply type that in the search box and you will get prompted to select a file, or you can type the file name directly. Then type your question and press <kbd>Enter</kbd>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 814px; " > <a class="gatsby-resp-image-link" href="/static/651042de665bf6ed228b76a388626396/a4262/file.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 158.14814814814815%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAYAAAASYli2AAAACXBIWXMAABYlAAAWJQFJUiTwAAAEfUlEQVR42m2W2XbiRhCG9Qg+XgEJAxJoA4FYBAzYYx/b8ULmzm+QuZpcJXn/cyr1ldTE48lFUdXb37X81cLLti8Sj0vJs1Si4VCKopBhoyeTiYxGI4njWMbjsURRJEmS2FyWZTZ2+zjDHm+QLWQwTGQYhXJ9fS1hOJBut3sECsNQBoOBHUAD4sa9Xs/Gbg+2N4xYjOTy8lIBEpnNSpmVM/ED3+ZaVy25uroyabUarXOXF5fmQKaRbTYbSdNU+v2+eKm6zm1sCjq+XAdd6aq0W+0GDGk30pJWu32Uju9LV0EHg1CCbk98PxCvzWKrJcN+T76sKtlVG9ks11IWc5lOkIUUKmO1i+lC8slUcs3beFaYzsaFJNlEPZ1KFJbixZorPIw1D398/y5//fO3/Pjzh3z79rscDm/y8vIsT08P8vj4YPZiPv9VFnNZLvWydCdep9OxXOXRSNbLlSznC1ktVCPLSnWlBxp7pVqjWFaqq6q2EV1brdYKuBePypDcnsafKmgSxRKrjMJEwn5sMhiohGrrfKgRRfHIdDjU+aheY0+vm4gHdyg5SR6EdemNCkqjQCsddINaB432G/snXYsftMWDBoTMIrwbNTkFFI2MRvGRl0g9V++D6FAGgY/mIQuOa4Sf57lJOaeyUwPhIB3BfjTrU12DIScnJ3J6emrnPTbiHQtoPJuVpUz0wCxNZJpC9pms12tN/lLmVlUtnBaIeeYYY3Pe44dWc3zEZnGinuR6WdF4Q8+WehHe4hljwsQmKodhISOA4TILHORWFzYXMIdXAGAz54BdugwQIF8rxYBJbDYDyIvC7a4gpAPbadKFzbmLiwtzysNtFhggvCAOEA9dSmraBEIjcAmCzRpnoB62R8UYcAsbeDFcKISGVNoVFANhnvF2u5VpkwpLjWqjDV4RrgsZUAA5zGUIm4ca4rTJG3NJmslUwQmdcI8hs0h+7O1rqMMhQgYUWsDFuaalyjPJtINWqiuePQ015XVv+GhFoQifiwKIqybA5rHaeFSSX72o0orPm5TAUZwyYlMU3HZlJ4cOEDA2EwXhVmqTO3LFXucMaXJp81zyyYFrIb9pfOe5SwcHmXOc/SwG6B4HKr3f72W321kl8ZDq4ZFVVMes4TEeuks+igEC4r5or6+v8vb2Jg8PvNCPdsH7+7u+1C82fn5+tj10FhG5dv0JEDBIysTZ2ZmF7Ghwfn5+nPuoOQiJXWo+igfLXfvRauTTFYTPI2HXvVy/LO4bzDkK+FGY8wCh0txK7ngA7u/v5ebmxnL29e5e7lSefns2XqZK6KL4j6eu9Zx4hOUqBMfW+gH6okXY72/s8Gq9lcV6I/uvd/Z1i0eRRpJY/+O9+wdxBDwcDuYRwHP96j2+HGS1wbNbub3ZqcdLLciterxW2cjt7VYrXRo4KYGj9pFzgLhOqPzNiLOxFPqRz6dz/bDn6m39FiL8TbGXSSXQfxbuUf0IZoBUpmZ9/Uz5nbZ+yTrNU+Ufu4G1bjf4JWdOKArgRw8hqmsfx6//G38m82f5F50ImtN4ShJhAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Using files as context" title="" src="/static/651042de665bf6ed228b76a388626396/a4262/file.png" srcset="/static/651042de665bf6ed228b76a388626396/01bf6/file.png 270w, /static/651042de665bf6ed228b76a388626396/07484/file.png 540w, /static/651042de665bf6ed228b76a388626396/a4262/file.png 814w" sizes="(max-width: 814px) 100vw, 814px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="understand-errors-in-terminal" style="position:relative;"><a href="#understand-errors-in-terminal" aria-label="understand errors in terminal permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Understand errors in terminal</h2> <p>When you get any errors or issues in your terminal, select the error text and press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd> and you will get an explanation:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/821cb30d4dd42dfbf54bfb70b1814401/2bfae/terminalerror.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 48.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABuElEQVR42mVS7Y6jMAzkHVY0pYWEkITwWaBbtqraql2pf/r+DzRrB3Hau/sxMnHweMZO5LxHVVVwzoVYOh++pVLI85ygoLWGMSbEJZcjy7K/UBQF5TWiz+MRz+cTl8sFp3mGpcKy6eHaAb5pUbUNtHFQRCJlRpD/I5N0r9A0FtFMJO/3G6/XC+fzGZo62bpDM5zQTp9E3sGUJXWnIlL9Gyvhei60QjRNEx6PB263WyAsTAFXkbJ+Qj0QDiOsK/9YZev8D1u01gZwLg8OJKIjWWZ11+sVrNZaA1838N2Icf7C6XKG9TWRUJGSYY6/wcQcV5VRXdcYhgFt24al5HRpfYu6PwZ1VT/C+SoQLkuhZakF/9oPhLzR1c5qyZQe2vqgzDAZbb6wvHmNNN1jv1+QpmnAShYss0KeIyvk9feNxzweMJLtjp6U2u2QCgGZJAEq2SHnHH1vt1vCEhM+EyJPRUzYd91il7ocCnp3iYDeCiJIAgFDig2UiKHiOOTFJsYm/oCghoLuGBHbZNs8XEkK86rE9PWgZ3NH1YxhEVyw3yWYxhPmw51exJ2sZlB2gum+IbVFVtBIcosfkCgcf+5wqSkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Getting terminal errors explained" title="" src="/static/821cb30d4dd42dfbf54bfb70b1814401/302a4/terminalerror.png" srcset="/static/821cb30d4dd42dfbf54bfb70b1814401/01bf6/terminalerror.png 270w, /static/821cb30d4dd42dfbf54bfb70b1814401/07484/terminalerror.png 540w, /static/821cb30d4dd42dfbf54bfb70b1814401/302a4/terminalerror.png 1080w, /static/821cb30d4dd42dfbf54bfb70b1814401/0d292/terminalerror.png 1620w, /static/821cb30d4dd42dfbf54bfb70b1814401/b3608/terminalerror.png 2160w, /static/821cb30d4dd42dfbf54bfb70b1814401/2bfae/terminalerror.png 3804w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>This was another exciting step towards enabling everyone around the world to use AI in their day to day tasks without having to pay any fees or be dependant to any cloud provider. I hope you enjoyed and could get the setup up and running, and stay tuned for more AI content.</p><![CDATA[🔮 fabric, augmenting humans using AI]]>https://yashints.dev/blog/2024/06/01/fabrichttps://yashints.dev/blog/2024/06/01/fabricSat, 01 Jun 2024 00:00:00 GMT<p>If you have worked with <a href="https://openai.com/" target="_blank" rel="nofollow noopener noreferrer">OpenAI</a> or any other <a href="https://en.wikipedia.org/wiki/Generative_artificial_intelligence" target="_blank" rel="nofollow noopener noreferrer">Generative AI</a> model aka <em>LLMs</em>, you would know that writing a good <strong>prompt</strong> is a very key part of how we interact with those models. However, it’s often ignored which most probably means people are not getting the most out of these super capable models.</p> <!--more--> <h2 id="the-why" style="position:relative;"><a href="#the-why" aria-label="the why permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The why</h2> <p>The most important part of interacting with LLMs is writing a good prompt, and that’s why it is important to make sure not only you know how to write a good prompt, but also are aware of what tools are available which might help you do just that. I wrote this post to introduce <a href="https://github.com/danielmiessler/fabric" target="_blank" rel="nofollow noopener noreferrer">fabric</a> to you all since it has changed the way I personally use my local models and I can’t believe how did I not know about it before.</p> <h2 id="introducing-fabric" style="position:relative;"><a href="#introducing-fabric" aria-label="introducing fabric permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Introducing fabric</h2> <p><strong>fabric</strong> is an open source tool which augments humans using AI. I know it sounds a little fancy, but believe me when you get to know its features and usefulness, you would totally be onboard with the definition.</p> <p>In short, fabric is a tool which not only has many patterns (a comprehensive prompt for a specific purpose) already baked in, but also allows you to use them with LLMs like OpenAI or even local LLMs (<a href="/blog/2024/05/28/local-llms/">read my last post on how to install Ollama</a>) if you have them handy. Furthermore, you can add your own patterns if you like to the mix too and contribute back to the community if your pattern deemed useful to others.</p> <p>Enough talking, let’s get it installed and show you some cool features.</p> <h2 id="prerequisites" style="position:relative;"><a href="#prerequisites" aria-label="prerequisites permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Prerequisites</h2> <p>You would need a few things installed to be able to seamlessly install fabric. I am using WSL to install it, but you can install it in a <em>linux</em> distro of your choice.</p> <ul> <li><strong>pipx</strong>: fabric uses pipx to install itself, so get it installed by running: <div class="gatsby-code-button-container" data-toaster-id="51675442216674990000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`sudo apt install pipx`, `51675442216674990000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> pipx</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> </li> <li><strong>python3-dev</strong>: Since it is using Python extensions, you would need to install the header files: <div class="gatsby-code-button-container" data-toaster-id="49138736706630115000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`sudo apt-get install python3-dev`, `49138736706630115000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> python3-dev</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> </li> <li><strong>ffmpeg</strong>: Fabric depends on this library and <strong>libavcodec-extra</strong> when handling multimedia: <div class="gatsby-code-button-container" data-toaster-id="86824776593957060000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`sudo apt-get install ffmpeg libavcodec-extra`, `86824776593957060000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> ffmpeg libavcodec-extra</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> </li> </ul> <h2 id="installing-fabric" style="position:relative;"><a href="#installing-fabric" aria-label="installing fabric permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Installing fabric</h2> <p>You wouldn’t believe how simple the installation process is. First navigate to a folder where you intend to clone the repository, clone the repo and then navigate into the fabric folder:</p> <div class="gatsby-code-button-container" data-toaster-id="83135264709770700000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`cd /where/you/keep/code # if the path is a mounted drive you might have to run this with sudo git clone https://github.com/danielmiessler/fabric.git cd fabric`, `83135264709770700000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token builtin class-name">cd</span> /where/you/keep/code <span class="token comment"># if the path is a mounted drive you might have to run this with sudo</span> <span class="token function">git</span> clone https://github.com/danielmiessler/fabric.git <span class="token builtin class-name">cd</span> fabric</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div> <p>Now use <em>pipx</em> to install fabric:</p> <div class="gatsby-code-button-container" data-toaster-id="76392123523447910000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`pipx install .`, `76392123523447910000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">pipx <span class="token function">install</span> <span class="token builtin class-name">.</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Once installed, call the setup function where you have provide four API keys for OpenAI, Claude APIs, Google and YouTube APIs which you can get them from their respective consoles.</p> <div class="gatsby-code-button-container" data-toaster-id="86980674132153600000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`fabric --setup`, `86980674132153600000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">fabric <span class="token parameter variable">--setup</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>When you are finished, you need to exit bash and go back in to have access to fabric:</p> <div class="gatsby-code-button-container" data-toaster-id="84884581177546980000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`exit`, `84884581177546980000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token builtin class-name">exit</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>And in your terminal:</p> <div class="gatsby-code-button-container" data-toaster-id="33645144932961178000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`bash`, `33645144932961178000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="powershell"><pre style="counter-reset: linenumber NaN" class="language-powershell line-numbers"><code class="language-powershell">bash</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Now you can run fabric and see the list of available patterns:</p> <div class="gatsby-code-button-container" data-toaster-id="7337565610330676000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`fabric --list`, `7337565610330676000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">fabric <span class="token parameter variable">--list</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Which should show you the list of available patterns.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/35df9457c4ff4bceaf77c02c94cf0afe/6edca/fabric-list.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.18518518518519%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABgUlEQVR42p1T2W7CMBDMF/QAUaClIRDClZA4zuE4IQEK5aWq1P//manXhapIVG3yMFpblkezM7tGtXvF2/sHdvsjsryEUPB5gjAR8HwOxlOEsQCLvmoYCcj1Rr2FsGcuxnMfo+kKj9YUo9kKRpiu4SwZJgqOG6LzNEa7N0S7b6Glaut0PqPVs66+6bOCkeQbMEUaJAVkdUDPnOCua35/+In77mW9BiOIcxD8SCKWG5j2Un0wf/3wFwymlLETKc9K9E0HN51Bc0JZ7ZEWO1Cllv1YwlZGN1WpPYyyClyUuk49jmd70ZzQC4X2b8Uz0NllAtbUU8ldJv1vwlhWCMUaND6kMi22WAaJHp/bBl4aWak8zLfax6x8gVT3SBEPRnMMxnOttBYhqSJ1lDZV8pFUEhZ+jIeBXd9Dl6V6dHwu9cbQLFqOB3Oy1CtVJyCD2hOqXVJKZEPHRX/onDbCrJ22Qa3RDHIVTLE9It8cNM7B1CYMklx7R2Oj91p5SRZQKE1m8RMKKaJKvrPzSQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Getting the list of available patterns from fabric" title="" src="/static/35df9457c4ff4bceaf77c02c94cf0afe/302a4/fabric-list.png" srcset="/static/35df9457c4ff4bceaf77c02c94cf0afe/01bf6/fabric-list.png 270w, /static/35df9457c4ff4bceaf77c02c94cf0afe/07484/fabric-list.png 540w, /static/35df9457c4ff4bceaf77c02c94cf0afe/302a4/fabric-list.png 1080w, /static/35df9457c4ff4bceaf77c02c94cf0afe/6edca/fabric-list.png 1351w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="features" style="position:relative;"><a href="#features" aria-label="features permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Features</h2> <p>Now let’s explore some cool features that will change they way you use LLMs forever. But first, we need to set our default model to make sure we are not charged by OpenAI or other available online models:</p> <div class="gatsby-code-button-container" data-toaster-id="22542208310267896000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`ollama list # replace the name of the model with whatever you have available fabric --setDefaultModel llama3:latest`, `22542208310267896000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">ollama list <span class="token comment"># replace the name of the model with whatever you have available</span> fabric <span class="token parameter variable">--setDefaultModel</span> llama3:latest</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>Now we’re ready to go. First off, let’s use one of the features which I use almost every day. I have subscribed to many YouTube channels be it tech or not, and many of those post long videos on a daily basis. You can summarise the whole video using one command:</p> <div class="gatsby-code-button-container" data-toaster-id="25596707080494686000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`yt --transcript https://youtube.com/watch?v=uXs-zPc63kM | fabric --stream --pattern extract_wisdom`, `25596707080494686000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">yt <span class="token parameter variable">--transcript</span> https://youtube.com/watch?v<span class="token operator">=</span>uXs-zPc63kM <span class="token operator">|</span> fabric <span class="token parameter variable">--stream</span> <span class="token parameter variable">--pattern</span> extract_wisdom</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>We’re using the <code class="language-text">extract_wisdom</code> pattern here, but you can use any other pattern you want. The API key you provided for YouTube is used here to get the transcript and pass it to the LLM using the pattern you have chosen. Super cool and fast response makes this a really great setup.</p> <p>Let’s try something different:</p> <div class="gatsby-code-button-container" data-toaster-id="62348650753619890000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`echo &quot;An idea that coding is like speaking with rules.&quot; | fabric -sp write_essay`, `62348650753619890000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token builtin class-name">echo</span> <span class="token string">"An idea that coding is like speaking with rules."</span> <span class="token operator">|</span> fabric <span class="token parameter variable">-sp</span> write_essay</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Here we’re passing an idea and ask fabric to call our model and ask for an essay. The result will truly amaze you.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/4e68d6bbc83aa0b72e598ada2b89ab6a/f1f01/fabric-essay.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABm0lEQVR42jWS3XKbMBCFeZLWiTEgQBLi3wa7buOpO81FOrnITN7/QU7PEfHFeiVZnP32rJLb/RXr9Yb1xwum5QLb9LBhhO8mtOMJoZ+RGoddZrE3HlndorA9DlWHwg+owgzjR+Rc73KH5Ik/aRl4sUPOy4eKof1XfqbIY59TyPDDut1E6va4RXdCxbznvaRqRigK28G4Hq6bUYeJZxMLdNwfUao6CUUnSglnjEJkbohitl94NiDJKZRFshA/1Do1DakaUoWY068QZclClkSGWYISc8MKP55Rch0JC5JJLAb3ypZtRb/439biEIUUzXSOAs10QTNfMKwvMfx4QSIKRf4wmz7sCx/b15kKSixXUVph+80vP65wFFWrw/oL3eknTCChPBKNqB5riRl6o6x9DJFRRIKBVO3pGgnn+x+0H+9o7n8RjlckEtJAvqUlnguH72mF3aHGU263dVbz3EOvQS9AQ5B/hs+lalj89hvZ5weWt3/08oxEFAe2JmG1uNFthLJAQ9Mg/LAgpTUKiWrKuYbSLSjZriN13Z/xH0lDGTrscRk5AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="writing an essay based on a prompt via fabric" title="" src="/static/4e68d6bbc83aa0b72e598ada2b89ab6a/302a4/fabric-essay.png" srcset="/static/4e68d6bbc83aa0b72e598ada2b89ab6a/01bf6/fabric-essay.png 270w, /static/4e68d6bbc83aa0b72e598ada2b89ab6a/07484/fabric-essay.png 540w, /static/4e68d6bbc83aa0b72e598ada2b89ab6a/302a4/fabric-essay.png 1080w, /static/4e68d6bbc83aa0b72e598ada2b89ab6a/0d292/fabric-essay.png 1620w, /static/4e68d6bbc83aa0b72e598ada2b89ab6a/b3608/fabric-essay.png 2160w, /static/4e68d6bbc83aa0b72e598ada2b89ab6a/f1f01/fabric-essay.png 2212w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="taking-it-to-next-level" style="position:relative;"><a href="#taking-it-to-next-level" aria-label="taking it to next level permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Taking it to next level</h2> <p>There are two library in macOS called <code class="language-text">pbcopy</code> and <code class="language-text">pbpaste</code> which allow you to copy text from and paste text into your terminal, but at large. Fortunately we can leverage a library which allows us to do exactly that in WSL or any other linux distro called <code class="language-text">xclip</code>. Let’s get it installed and running:</p> <div class="gatsby-code-button-container" data-toaster-id="55998817889630855000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`sudo apt install xclip nano ~/.bashrc`, `55998817889630855000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> xclip <span class="token function">nano</span> ~/.bashrc</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>Now paste these two lines at the end of the file:</p> <div class="gatsby-code-button-container" data-toaster-id="66697224717259720000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`alias pbcopy='xclip -selection clipboard' alias pbpaste='xclip -selection clipboard -o'`, `66697224717259720000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token builtin class-name">alias</span> <span class="token assign-left variable">pbcopy</span><span class="token operator">=</span><span class="token string">'xclip -selection clipboard'</span> <span class="token builtin class-name">alias</span> <span class="token assign-left variable">pbpaste</span><span class="token operator">=</span><span class="token string">'xclip -selection clipboard -o'</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>Exit nano with <kbd>Ctrl</kbd> + <kbd>X</kbd>, then enter Y and press <kbd>Enter</kbd>. Last we need to refresh our terminal:</p> <div class="gatsby-code-button-container" data-toaster-id="9274365862766420000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`source ~/.bashrc`, `9274365862766420000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token builtin class-name">source</span> ~/.bashrc</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>We’re good to go. Navigate to any site which has a long article, I use <a href="https://www.abc.net.au/news/2024-05-30/rba-should-hike-interest-rates-in-june-but-will-it/103909992" target="_blank" rel="nofollow noopener noreferrer">this one talking about interest rates in Australia and what might happen in June</a>. Copy all the text in that page and then type this in your terminal:</p> <div class="gatsby-code-button-container" data-toaster-id="57436933276993176000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`pbpaste | fabric -sp extract_insights`, `57436933276993176000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">pbpaste <span class="token operator">|</span> fabric <span class="token parameter variable">-sp</span> extract_insights</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/cb088eba0d396238922477c8812e1fe5/e1190/pbpaste.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 26.666666666666668%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA/UlEQVR42j2P2U7DMBBF8xEIBI3akjhestjOYmdpAlRC/P8fXWYM6sPRjCfj45tsXHa4cUk0PuAiDArZolSE7iCI/F0hLwxOV4W3q0z9WTSJV5q9nCs85wJPpxKZ7iZ0w0osqGpPEgdBtdCWcKianuZ9+iapl+1AswHaBhgfUfsZLd2V3Yi8bJAN84E+7o9Lf1ILYfy/zEPTsiKRbEcYF6EoBEu7cYN2IVVD4ou0yOy4wk0b2n4G9+kcbvAE94aScGr+E0GPGhLUtMuwhGWMiweUjciW7Qv75zfCcqCmZUVpGlpkAafis3ykCylRvN1x3H8Q1g9MxDDvqdppxS9sE5k7BtHZ4AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="using pbpaste to get an entire article in the terminal and pass it to fabric" title="" src="/static/cb088eba0d396238922477c8812e1fe5/302a4/pbpaste.png" srcset="/static/cb088eba0d396238922477c8812e1fe5/01bf6/pbpaste.png 270w, /static/cb088eba0d396238922477c8812e1fe5/07484/pbpaste.png 540w, /static/cb088eba0d396238922477c8812e1fe5/302a4/pbpaste.png 1080w, /static/cb088eba0d396238922477c8812e1fe5/0d292/pbpaste.png 1620w, /static/cb088eba0d396238922477c8812e1fe5/b3608/pbpaste.png 2160w, /static/cb088eba0d396238922477c8812e1fe5/e1190/pbpaste.png 2203w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="what-next" style="position:relative;"><a href="#what-next" aria-label="what next permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What next</h2> <p>There are many cool features you might be interested in, but I just highlight two more which I think are the most practical. Setting a <em>context</em>, and adding <em>custom patters</em>. Setting a context is done by adding a markdown file called <code class="language-text">context.md</code> in the <code class="language-text">./config/fabric/</code> directory to add context to your pattern. This could be anything you like, for example you might add your goals or the essence of your existence and why you do what you do day to day.</p> <p>From the custom patterns perspective you can always add your custom patterns which you do not want to upload to fabric in the <code class="language-text">~/.config/custom-fabric-patterns</code> directory. However, whenever you want to use them with fabric you need to move them into the <code class="language-text">~/.config/fabric/patterns</code> directory. Just beware that any time you update fabric, the folder get updated and every custom pattern will be deleted, so you need to copy them from the custom folder before using them.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>Hope you now have a good cause to go setup fabric and spend a little bit more time with your local LLMs and get better at writing prompts and extracting information or getting what you need faster and more effectively. Stay tuned for more AI content 👋🏽.</p><![CDATA[🥇 Run your generative AI model locally 🥇]]>https://yashints.dev/blog/2024/05/28/local-llmshttps://yashints.dev/blog/2024/05/28/local-llmsTue, 28 May 2024 00:00:00 GMT<p>Many learners ask me how they can get access to <a href="https://azure.microsoft.com/en-au/products/ai-services/openai-service" target="_blank" rel="nofollow noopener noreferrer">Azure OpenAI</a> and <a href="https://en.wikipedia.org/wiki/Generative_artificial_intelligence" target="_blank" rel="nofollow noopener noreferrer">Generative AI</a> since they don’t have access to an enabled subscription. I knew there are open source tools which allow people to work with these models, but I never had a use case to go and try this out.</p> <!--more--> <h2 id="-the-need" style="position:relative;"><a href="#-the-need" aria-label=" the need permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>🩺 The need</h2> <p>I have a kid in high school and she wanted to give <a href="https://chat.openai.com/" target="_blank" rel="nofollow noopener noreferrer">ChatGPT</a> a try for a task she was working on, I was afraid she would use it to get the answer right away instead of figuring out how to solve the problem. This forced me to look into how I can have a better control over how she could use LLMs without “cheating”.</p> <p>That’s when I stumbled upon <a href="https://ollama.com/" target="_blank" rel="nofollow noopener noreferrer">Ollama</a>, a great cross-platform, open source project which happens to be the easiest way you can get an engine running with many available models. Although their Windows version is experimental and in development, you can install it on <a href="https://learn.microsoft.com/en-us/windows/wsl/about" target="_blank" rel="nofollow noopener noreferrer">Windows Subsystem for Linux aka WSL</a>.</p> <h2 id="️-prerequisites" style="position:relative;"><a href="#%EF%B8%8F-prerequisites" aria-label="️ prerequisites permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>🖥️ Prerequisites</h2> <p>Before we start our process, you need to know that you will need a system which has a decent GPU since running AI locally demands that. This could even be your laptop which has a graphic card, or even better your gaming PC with that crazy spec you spent that much money on.</p> <p>You most likely need docker installed as well since I will use it to run the web UI, but this is not mandatory.</p> <h2 id="️-installation" style="position:relative;"><a href="#%EF%B8%8F-installation" aria-label="️ installation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>⚙️ Installation</h2> <p>Of course you need WSL enabled and a distro installed, but don’t worry as soon as you enable <strong>Virtual Machine Platform</strong> and <strong>Windows Subsystem for Linux</strong> in your windows features, all it takes is a few commands:</p> <div class="gatsby-code-button-container" data-toaster-id="75297141955843490000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`wsl -- install -d ubuntu wsl --update wsl --set-default-version 2`, `75297141955843490000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">wsl -- <span class="token function">install</span> <span class="token parameter variable">-d</span> ubuntu wsl <span class="token parameter variable">--update</span> wsl --set-default-version <span class="token number">2</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>Before we install any packages, it’s always a good idea to update the current ones, this becomes more important if you have had WSL enabled for a while.</p> <div class="gatsby-code-button-container" data-toaster-id="35780843537718910000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`sudo apt update && sudo apt upgrade`, `35780843537718910000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> update <span class="token operator">&amp;&amp;</span> <span class="token function">sudo</span> <span class="token function">apt</span> upgrade</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Now it’s time to install Ollama:</p> <div class="gatsby-code-button-container" data-toaster-id="80519799398246300000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`curl -fsSL https://ollama.com/install.sh | sh`, `80519799398246300000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-fsSL</span> https://ollama.com/install.sh <span class="token operator">|</span> <span class="token function">sh</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>This will take a while to finish, so get a coffee and be patient. Once it is installed it will start the Ollama engine and shows you it’s running. To test it you can open a browser and navigate to <code class="language-text">http://localhost:11434</code> which should show you it’s running:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 487px; " > <a class="gatsby-resp-image-link" href="/static/a1ef1bb866740823f28c1122cc093f34/7b439/ollama-running.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 34.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABa0lEQVR42o2OWU/bUBCF/UwdZ6NCJSAUoqQxDkGNs1wHsUbgBMeBbDSYkAXMvrV9qvjzH9dUrcRD1T58OnPmjDRHsepHCLtFZa/5i30Xc7eB2G9Rqh1gbtcxdxoUtmwK27b0NkWZF+WutHtAuea8Q+ldPtI8u6LuXeBIbZz6TL/9pNYf0Z7c0B5fYXc8umc+naGPOxixd/SVes9jy2ljyRJVWeI3yvHtDw7P72mOb2hN73And0y+v2B7Pv3LJ/r+A+7JlOH1MwN5153IB70hrjel1h6w4XTYbHb/oOiFMnqhQqBG0Xoj8Dmpn9eKZNZMsl9K6JIVeRMQZIYpyJUsVsvVdyipZJJsJo2uZ1mY/8RiIsHcx1mSS0tk0mlSqWUWFxJoqko4FHpDC6l/Rcnn81REBbvhkMutYBgGy/KJEAJRXUdYAsuyiEajxOPxf6JomkZIfv0wM0Mwh8MakUgEVe5U2SrIAh+Lxf6LV8no4FvZ7msgAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Ollama running in WSL" title="" src="/static/a1ef1bb866740823f28c1122cc093f34/7b439/ollama-running.png" srcset="/static/a1ef1bb866740823f28c1122cc093f34/01bf6/ollama-running.png 270w, /static/a1ef1bb866740823f28c1122cc093f34/7b439/ollama-running.png 487w" sizes="(max-width: 487px) 100vw, 487px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Now you can pull down a model and interact with it:</p> <div class="gatsby-code-button-container" data-toaster-id="42559587643281450000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`ollama run llama3`, `42559587643281450000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">ollama run llama3</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Once it’s done, it will wait for you to enter your prompt:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/898b49ba227947039dc7028e714f2526/b4904/ollama-waiting.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 5.185185185185186%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAU0lEQVR42h3DMQqAIBiA0S7RUg0WIhGmqZQODeX+n6Kh+5/gC3rwGhGh3pVSCjlnQoiklPDeY63FOUc8ds554+0vnsUha0vsBsZpQmv9N8aglOIDD9wfYloWc+AAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Ollama waiting for a prompt" title="" src="/static/898b49ba227947039dc7028e714f2526/302a4/ollama-waiting.png" srcset="/static/898b49ba227947039dc7028e714f2526/01bf6/ollama-waiting.png 270w, /static/898b49ba227947039dc7028e714f2526/07484/ollama-waiting.png 540w, /static/898b49ba227947039dc7028e714f2526/302a4/ollama-waiting.png 1080w, /static/898b49ba227947039dc7028e714f2526/0d292/ollama-waiting.png 1620w, /static/898b49ba227947039dc7028e714f2526/b4904/ollama-waiting.png 1768w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>And upon <kbd>Enter<kbd> it shows you the response:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/508037ed321b1d8cdc8a23680350a727/54421/pentagons-area.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 32.96296296296297%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAz0lEQVR42nWRSxKEIAxEuYXAKF9F3Ize/3IZOiCl1rjoSgzy0iFi3790HEdXSomC87QURe/I2SLnKARP1lpSSpGU8lUixkjLstA8z4Qcl40xZMplADhvwnc9r3Xk0FlHFOu6Us6ZEMdxfHXwr47aUwzcto1yERwCOk0Td+PO3rMb1K8ufanjv2EY7iNjXEBDCDw6IgT42VWquyOtNQs5gFeowBLWVICxgAokNgEKF6f62zaXz3fFBAAzMM51MXUUx7C62dDBWn+6s6c+7QwOf/ssvVdPUOA3AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Ollama answering to prompts using Llama3 model" title="" src="/static/508037ed321b1d8cdc8a23680350a727/302a4/pentagons-area.png" srcset="/static/508037ed321b1d8cdc8a23680350a727/01bf6/pentagons-area.png 270w, /static/508037ed321b1d8cdc8a23680350a727/07484/pentagons-area.png 540w, /static/508037ed321b1d8cdc8a23680350a727/302a4/pentagons-area.png 1080w, /static/508037ed321b1d8cdc8a23680350a727/0d292/pentagons-area.png 1620w, /static/508037ed321b1d8cdc8a23680350a727/b3608/pentagons-area.png 2160w, /static/508037ed321b1d8cdc8a23680350a727/54421/pentagons-area.png 3009w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="running-as-service" style="position:relative;"><a href="#running-as-service" aria-label="running as service permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Running as service</h2> <p>Another issue I was facing now was that I didn’t want to keep my terminal running, so a better way would be to run Ollama as a service. Since WSL does not use the <code class="language-text">systemd</code>, you can’t run services by default. So we have to first enable it:</p> <div class="gatsby-code-button-container" data-toaster-id="17593283199507814000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`nano /etc/wsl.conf`, `17593283199507814000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">nano</span> /etc/wsl.conf</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Then enter these lines anywhere in the file:</p> <div class="gatsby-code-button-container" data-toaster-id="14650802523029550000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`[boot] systemd=true`, `14650802523029550000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token punctuation">[</span>boot<span class="token punctuation">]</span> <span class="token assign-left variable">systemd</span><span class="token operator">=</span>true</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>And finally exit the nano with <kbd>Ctrl</kbd> + <kbd>X</kbd>, select Y to save your changes. Now we have to restart our WSL. Exit our of WSL by typing exit, and then run:</p> <div class="gatsby-code-button-container" data-toaster-id="72017630228423690000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`wsl --shutdown wsl -l wsl run Ubuntu-22.04`, `72017630228423690000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">wsl <span class="token parameter variable">--shutdown</span> wsl <span class="token parameter variable">-l</span> wsl run Ubuntu-22.04</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <div class="custom-block info"><div class="custom-block-body"> <strong>Note:</strong> Replace the name of the distro with whatever you have which is shown from the list command.</div></div> <p>And finally start the service:</p> <div class="gatsby-code-button-container" data-toaster-id="53763259411597100000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`sudo systemctl enable ollama sudo systemctl start ollama`, `53763259411597100000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">sudo</span> systemctl <span class="token builtin class-name">enable</span> ollama <span class="token function">sudo</span> systemctl start ollama</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <h2 id="-user-interface" style="position:relative;"><a href="#-user-interface" aria-label=" user interface permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>📱 User interface</h2> <p>This is great, but my kiddo can’t use my terminal to interact with these models. So I went on a hunt to find a user interface which can use Ollama, and not long after I found <a href="https://openwebui.com/" target="_blank" rel="nofollow noopener noreferrer">Open WebUI</a> which allows you to leverage Ollama and interact with it via a user friendly UI. Plus it has the benefit of persisting your conversation history.</p> <p>You can install it directly on the WSL, but I’d much prefer to use the docker version of it, but first you need to enable the WSL integration on the Docker Desktop app:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/a2008c9803a34cfc3f3665c7f0630bc6/99375/wsl-docker.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 68.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAABj0lEQVR42pWT6W7jMBCD/Q7bnLYlH5Is+U7SAn3/F+NyxmmALbZo8uODZDgmSM4kW94/0cYBf44FdmfzD2+nUs99bp8m8/2EblzRdIMKC5VPqEkbR5zKGrtXBE13o+CiRAqHYSa8TytcGl9yp4JVN6OJE2o6bOKCOowwbSARR7p7WdC6COsSY/bImx6lo3BIrKB/ub/NYbphWD/Upe9n+DThZBod0oEOD4VQPS94rrzGNXRahZ6R5b45zesAeS88K5qdbQvrt8iWlH7G0UYcTGBk+1ih/beV2v/n3OcGWdkETvjCuAvXhCs0XXXCQk3HJ9PeaZTjF+W3O5F6MhO4KhR07K4lsoM5I2pc6/SDhyiftwqcvnvc78hvs9JN6Jd3pPmG8fLBHVw1vnRZCemCsk0oVKTB7v7v+YnsUFgOZVR3EtlzqR3jyykLLnUImmK40oXTrn7+p3C6+oF+vKrDyB5F/NxOyIl0s8s55WLr6ZcpO+7gTJfT/Rwfz1Ugeo4omg5F7RXptyC5Pm9dfwn+BWwVgBd9ZWaVAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Enabling WSL integration in Docker Desktop" title="" src="/static/a2008c9803a34cfc3f3665c7f0630bc6/302a4/wsl-docker.png" srcset="/static/a2008c9803a34cfc3f3665c7f0630bc6/01bf6/wsl-docker.png 270w, /static/a2008c9803a34cfc3f3665c7f0630bc6/07484/wsl-docker.png 540w, /static/a2008c9803a34cfc3f3665c7f0630bc6/302a4/wsl-docker.png 1080w, /static/a2008c9803a34cfc3f3665c7f0630bc6/99375/wsl-docker.png 1525w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Once you have that done, simply run the docker command:</p> <div class="gatsby-code-button-container" data-toaster-id="6781245797722457000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`docker run -d -p 3005:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main`, `6781245797722457000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">-d</span> <span class="token parameter variable">-p</span> <span class="token number">3005</span>:8080 --add-host<span class="token operator">=</span>host.docker.internal:host-gateway <span class="token parameter variable">-v</span> open-webui:/app/backend/data <span class="token parameter variable">--name</span> open-webui <span class="token parameter variable">--restart</span> always ghcr.io/open-webui/open-webui:main</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Note I changed the default port to 3005 since I have a few other apps running on port 3000 in development. Once the container is up and running all you need to do is to open a browser and navigate to <code class="language-text">http://localhost:3005</code>:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/368ee4ee03bfeba427f8f1cea9a080e0/32ac3/openwebuilogin.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 78.51851851851852%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAACB0lEQVR42oVU2W7iQBBE4r7MbQMLRmAMxg4iCogshwDBAwjxwv9/TGWqlSHGm00eStMz7qme6sOxYrEIDcMwkMlkEI/HkUwmxU6lUmJrpNNp+Z5IJGSfz+cR5oiFybLZLGazGa7XK7bbLabTKVarFTabDQ6Hg6zL5RL3+x3n8xmXywWDwQC5XE7ufxEWCnLI6I7j4Ha7YbfbYTweC8FiscB+vxcwyOl0wvF4lCC2bT8TFhRZpVJBz+6h2+3KKymHUmlzDcvWKdGSSVb8JHsQlstlBEEAb+LBKJVkT5SUzWBEtVoVhG2irPYlRVgKSyZprVbDaDSGp2ROJhOR66u12Wz+CMs0MZwv0O71UFAFiumC1Ot1cTCVQ7vVQotQe57/Bkv51tT6lMO3tzn+vr9j9voqcrMqV8xPtC2+Q175Fj79Hm1jWib6/YFUrdPtSIFsu4tOpwPLstBoNL4FX/hHdUZDqWG3PAh5iXlj24xGI7iuK7mk3W63v3Km/MJgivrBC5oq8BMh8xiFPmdatHRWPgpDfTeik8KnMxplcNVg9R1niMAPpFfDfhrip4uiCfkhLIOgTcIXGcG1GjNHEdZghoLqO7z/zyxrsOokksYtV6B7ld/0q3TjR1MUi7YALzKi53lwh65M0JAF8n34/kSKtVqv1XzPVa82//+3iZLSUSO654zr8+jdD0cPkQWTUrV7AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Open WebUI login page" title="" src="/static/368ee4ee03bfeba427f8f1cea9a080e0/302a4/openwebuilogin.png" srcset="/static/368ee4ee03bfeba427f8f1cea9a080e0/01bf6/openwebuilogin.png 270w, /static/368ee4ee03bfeba427f8f1cea9a080e0/07484/openwebuilogin.png 540w, /static/368ee4ee03bfeba427f8f1cea9a080e0/302a4/openwebuilogin.png 1080w, /static/368ee4ee03bfeba427f8f1cea9a080e0/32ac3/openwebuilogin.png 1249w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Click on sign up and add your user, don’t worry the password is only stored locally inside the docker container volume. Once you signed up, you will need to add the Ollama address in the settings:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/26b12b6d378269593fe0ceae46f386af/e1190/openwebuiollama.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 70%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAABR0lEQVR42q2TyW6DQBBE5wsidrOJzSwGjEEWueYUyYdw5P9/pUK1ReQslrCcQ8FAd9e8bgZlmiZ834dt2+CaMgxDtD5vla7rUEEQoO97pGkqpq7rwvM82YTrraLPbreDYuHxeES23yMMQwnkeY6iKCThKvdm/V2EiOMYwzBA0zSopmkwzzM+pgmXywXjOKKqKvB9UZayu+M4UnyPjh0RxLIsKG8h7E8naZkBisQ0Pp/PEuu6DofDQXKyLPsldsaupGVeSJQkieCThoVMoBjrTt11LEvxOt9brYSs/zIsl/Y4i7W9W63ka/s/xRzexZCJNCQ2C+4Nf4toqmhS1w2iKPqT7mFDzqBtWyH8N8O6rp8m5OhkhnKwl2PxDKFjOzBfNJi6AUWTZz6Is1CFWYzX+R1Rm0Pxh+YJp4j8qCzTgheHGKY3BGWCT95vbgT39NUgAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Adding Ollama URL in the Open WebUI settings" title="" src="/static/26b12b6d378269593fe0ceae46f386af/302a4/openwebuiollama.png" srcset="/static/26b12b6d378269593fe0ceae46f386af/01bf6/openwebuiollama.png 270w, /static/26b12b6d378269593fe0ceae46f386af/07484/openwebuiollama.png 540w, /static/26b12b6d378269593fe0ceae46f386af/302a4/openwebuiollama.png 1080w, /static/26b12b6d378269593fe0ceae46f386af/0d292/openwebuiollama.png 1620w, /static/26b12b6d378269593fe0ceae46f386af/b3608/openwebuiollama.png 2160w, /static/26b12b6d378269593fe0ceae46f386af/e1190/openwebuiollama.png 2203w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Click <strong>Save</strong> and you’re ready to select a model and start chatting.</p> <h2 id="-custom-model" style="position:relative;"><a href="#-custom-model" aria-label=" custom model permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>🤖 Custom model</h2> <p>That got me through the initial setup and everything was great, but I still had the problem of how to prevent the model to give direct answers to my kid. Fortunately Open WebUI has the ability to create custom model files which basically allows you to set a system message for an existing model. This is exactly what I needed.</p> <p>Navigate to <strong>Workspace</strong> and select create a new model file, I named it Debra, and added the following custom prompt:</p> <div class="gatsby-code-button-container" data-toaster-id="354177881345041340" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`FROM llama3 PARAMETER temperature 0 SYSTEM &quot;&quot;&quot; You are an AI assistant designed to help my daughter which is in high school learn and solve problems. You will guide her through the process of finding solutions, but you won’t provide direct answers. This is to ensure that she is actively learning and understanding the concepts. Remember, the goal is not just to get the right answer, but to understand how to arrive at that answer. I want her on this journey of learning together in a safe and supportive environment. &quot;&quot;&quot;`, `354177881345041340`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="txt"><pre style="counter-reset: linenumber NaN" class="language-txt line-numbers"><code class="language-txt">FROM llama3 PARAMETER temperature 0 SYSTEM """ You are an AI assistant designed to help my daughter which is in high school learn and solve problems. You will guide her through the process of finding solutions, but you won’t provide direct answers. This is to ensure that she is actively learning and understanding the concepts. Remember, the goal is not just to get the right answer, but to understand how to arrive at that answer. I want her on this journey of learning together in a safe and supportive environment. """</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>I also checked the assistant and education boxes and saved my model.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/66f56828465d28441d6c66df1c46b7f1/87629/debra.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 73.33333333333334%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABjElEQVR42n2TWZKCYAyEOYTivoMbKAzulve/VsYvVlMMAz50hYT8WTvBarWyoihsMBhYv9//h16vZ6PRyPI8t9Pp5L6TycTtTb4BgeI4tuFw6EHbMJ1ObblcenAeN/lgD+bzuWVZZtvt1sIw9Cx1VO1NXfypkIxpmtrxeLT9fm+73c6SJPFvkoDD4WBRFLVWBujQK1wsFna/3w2JgR9NUAVtKCsk0Pl8ttls5g+puArZxuNxKetgSfiVLTPDy+XigZG3280lW0XSAf/w06blj45MktRbD0QJPVLQ6/XqEjweD9dlw0ffyDzP3vh5zz2xgHKpiEdAGUnwfD7LClkUnGU50AeJDuUYVxTF78VmHx6CKj3aKMH/brfrUkDvdDoG/UgQEB3K0LauoM7Hb9wTPksJPzNcr9e22WycgwAd0A6t4fPtinRJvhQqYnvQh/IxqhWgdmlLOqj7lDzkEl6vlw+f4DhwOSyIa+EhY0EnsWZJJ6IV7WpMHpBt8lPzY4tqV2eFztAVEF9tujrvX6yonnZe2dFXAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Adding a custom model file in Open WebUI" title="" src="/static/66f56828465d28441d6c66df1c46b7f1/302a4/debra.png" srcset="/static/66f56828465d28441d6c66df1c46b7f1/01bf6/debra.png 270w, /static/66f56828465d28441d6c66df1c46b7f1/07484/debra.png 540w, /static/66f56828465d28441d6c66df1c46b7f1/302a4/debra.png 1080w, /static/66f56828465d28441d6c66df1c46b7f1/0d292/debra.png 1620w, /static/66f56828465d28441d6c66df1c46b7f1/87629/debra.png 2076w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>And last we need to make sure her user can only use this model and can’t delete their chat history which can be done in the admin settings:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/c6063cefe87ddb1f2b5ddef872c725b3/b5c8e/lockuserdown.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 52.222222222222214%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABIklEQVR42pVSSW6EQBDjCUmAZh32HYFGMHMhpwiJYyR+kP9/wqkqCZREMNEcTNFN22UXrfmXC5IkQZGnUk3ThK7rO3j9HwzDgGVZUjXP91AUBYmlCKNIPlyoSRiGCIIAjuPAtu1T8HmlFBx656oxeRgGlGWBiEQiEm3bVvb6vt9JR2AB3/eR57nw2K1W1zXWdcWyLJjnGeM4ijOOz5WJj9yxIKfhKoIcqSzLvQOvb7c7pmlC13Xi4szh5nLjKUWCPEje/EmsqkoiV9QojmNxe4Ysy5CmqcxdHPLjb1fP8yhGINV13VNwzKZp0dDMKxqd/JQjwS3G5vwRZJ7EMV/omr2+HQs+A2XSlQnI6ccdXh79nuHTsCiJbsBNArx/fSK6VvgG5KMIFIS+eBgAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Locking user down to a custom model in Open WebUI" title="" src="/static/c6063cefe87ddb1f2b5ddef872c725b3/302a4/lockuserdown.png" srcset="/static/c6063cefe87ddb1f2b5ddef872c725b3/01bf6/lockuserdown.png 270w, /static/c6063cefe87ddb1f2b5ddef872c725b3/07484/lockuserdown.png 540w, /static/c6063cefe87ddb1f2b5ddef872c725b3/302a4/lockuserdown.png 1080w, /static/c6063cefe87ddb1f2b5ddef872c725b3/0d292/lockuserdown.png 1620w, /static/c6063cefe87ddb1f2b5ddef872c725b3/b3608/lockuserdown.png 2160w, /static/c6063cefe87ddb1f2b5ddef872c725b3/b5c8e/lockuserdown.png 2194w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>And we’re done. This gave my daughter a perfect tool which she can use with her homework without me worrying about how she uses AI for education purposes.</p> <h2 id="-summary" style="position:relative;"><a href="#-summary" aria-label=" summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>✍🏽 Summary</h2> <p>I hope you have enjoyed this guide and could setup your local LLM and use it to increase your productivity and others around you. Remember there are many more models you can leverage including CodeGemma and Phi3 from Microsoft. And last, you can use this with Semantic Kernel which is even cooler since I just finished my series on it.</p><![CDATA[Semantic kernel series - Last part - Planners]]>https://yashints.dev/blog/2024/05/27/semantic-kernel-plannershttps://yashints.dev/blog/2024/05/27/semantic-kernel-plannersMon, 27 May 2024 00:00:00 GMT<p>In my previous posts, I introduced you to foundational concepts of <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/" target="_blank" rel="nofollow noopener noreferrer">Semantic Kernel</a>, offering a glimpse into its potential. Then I showed you how to leverage plugins to communicate to LLMs, and as promised in this part we would review planners and native function plugins to show <em>Semantic Kernel’s</em> full potential.</p> <!--more--> <p>Semantic Kernel Series:</p> <p>✔️ Part one: <a href="/blog/2024/04/30/semantic-kernel">Intro</a> <br/> ✔️ Part two: <a href="/blog/2024/05/13/semantic-kernel-plugins">Plugins</a> <br/> ✔️ Part three: <a href="/blog/2024/05/27/semantic-kernel-planners/">Planners and Native Function plugins</a></p> <p><a href="https://github.com/yashints/semantic-kernel-devops" target="_blank" rel="nofollow noopener noreferrer">You can find the code on my GitHub repository here</a>.</p> <p>In out last two posts we successfully created some prompts for Azure DevOps and GitHub Action workflows and called Azure OpenAI to generate the YAML files and return the result. However, the result had multiple files separated by a special separator and that gives us a great opportunity to get into planners and native function plugins.</p> <h2 id="native-function-plugins" style="position:relative;"><a href="#native-function-plugins" aria-label="native function plugins permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Native function plugins</h2> <p>Previously we learnt how to create a plugin from prompt directory which allowed us to invoke one based on user input to get the desired results. But you don’t always have to use the text based approach, any function can become a plugin if you use a special decorator from the Semantic Kernel library.</p> <p>The result we got from our Azure OpenAI model had multiple YAML files separated by a series of characters. Let’s create a plugin which gets that output and creates one file per workflow in a given directory. All we need is to use the <code class="language-text">KernelFunction</code> attribute like following:</p> <div class="gatsby-code-button-container" data-toaster-id="26004689446923334000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`using Microsoft.SemanticKernel; using System.ComponentModel; class ExtrcatPipelineFromOutput { [KernelFunction, Description(@&quot;Saves a pipeline to a file. The pipeline is a YAML file.&quot;)] public static bool Extract(string modelOutPut, string separator, string path) { Console.WriteLine(modelOutPut); try { var chunks = modelOutPut.Split(separator); for (int i = 0; i < chunks.Length; i++) { if (chunks[i].IndexOf(&quot;FILE: &quot;) >= 0) { var fileName = chunks[i].Split(&quot;FILE: &quot;)[1].TrimEnd('\r', '\n'); SaveFile.SaveContentToFile(chunks[i + 1], Path.Combine(path, fileName)); i++; } } return true; } catch (Exception e) { Console.WriteLine(\$&quot;Error saving the pipeline: {e.Message}&quot;); return false; } } }`, `26004689446923334000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>SemanticKernel</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>ComponentModel</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">ExtrcatPipelineFromOutput</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">KernelFunction</span><span class="token punctuation">,</span> <span class="token class-name">Description</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">@"Saves a pipeline to a file. The pipeline is a YAML file."</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">Extract</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> modelOutPut<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> separator<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> path<span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>modelOutPut<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> chunks <span class="token operator">=</span> modelOutPut<span class="token punctuation">.</span><span class="token function">Split</span><span class="token punctuation">(</span>separator<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> chunks<span class="token punctuation">.</span>Length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>chunks<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">IndexOf</span><span class="token punctuation">(</span><span class="token string">"FILE: "</span><span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> fileName <span class="token operator">=</span> chunks<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">Split</span><span class="token punctuation">(</span><span class="token string">"FILE: "</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">TrimEnd</span><span class="token punctuation">(</span><span class="token char">'\r'</span><span class="token punctuation">,</span> <span class="token char">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> SaveFile<span class="token punctuation">.</span><span class="token function">SaveContentToFile</span><span class="token punctuation">(</span>chunks<span class="token punctuation">[</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> Path<span class="token punctuation">.</span><span class="token function">Combine</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> fileName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Error saving the pipeline: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">e<span class="token punctuation">.</span>Message</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>It’s that easy to create a kernel function which then can be included in a plan by the kernel. Don’t worry about the <code class="language-text">SaveFile</code> class for now, that just writes the content into a file in given path. You can look up the source for that in the GitHub repository.</p> <h2 id="planner" style="position:relative;"><a href="#planner" aria-label="planner permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Planner</h2> <p>Now that we have our function ready, let’s go ahead and create our planner. You might be asking what is a planner anyway, which I will answer nothing special. It’s a function which takes user’s ask and returns a plan created using AI and any plugins or kernel functions which are registered to accomplish the ask.</p> <p>There are two types of planners you can use at the time of writing this post (this is in preview so anything can happen):</p> <ul> <li><strong>HandlebarsPlanner</strong>: It’s a sequential planner which is using the <a href="https://handlebarsjs.com/guide/" target="_blank" rel="nofollow noopener noreferrer">Handlebars’ syntax</a> to generate a plan using a single call to LLMs. This has multiple benefits such as the ability to inspect a plan before execution or the ability to save it and load it later to save costs. In addition to those, since it’s using Handlebars it can have <strong>loops</strong> and <strong>conditions</strong> in the plan as well.</li> <li><strong>FunctionCallingStepwisePlanner</strong>: This one has the ability to use the ReAct model which allows the AI to make a function call, reason over it and make another call if necessary.</li> </ul> <div class="custom-block info"><div class="custom-block-body"> <strong>Note:</strong> In addition to this, the feature is also baked into the model and is called automatic function calling which does not require a planner at all, more on this at the end.</div></div> <h3 id="creating-the-planner" style="position:relative;"><a href="#creating-the-planner" aria-label="creating the planner permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Creating the planner</h3> <p>Let’s start by creating our planner and see how it will create a plan. We need a few things before we start:</p> <ul> <li>The directory which we need to save the pipelines into.</li> <li>The directory which we intend to save our plan into.</li> <li>The function which is going to be called (Azure DevOps or GitHub Actions).</li> <li>Adding the native function to our kernel as plugin.</li> <li>And finally the separator since our native function requires that to extract the pipelines from the output.</li> </ul> <p>First we can ready our directories from the <code class="language-text">appsettings.json</code> file:</p> <div class="gatsby-code-button-container" data-toaster-id="26869464626790363000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var planTemplatePath = Configuration!.GetValue<string>(&quot;PlanPath&quot;)!; var pipelineDir = Configuration!.GetValue<string>(&quot;PipelineDir&quot;)!;`, `26869464626790363000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> planTemplatePath <span class="token operator">=</span> Configuration<span class="token operator">!</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetValue</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token string">"PlanPath"</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> pipelineDir <span class="token operator">=</span> Configuration<span class="token operator">!</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetValue</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token string">"PipelineDir"</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>Next let’s add our function to our kernel:</p> <div class="gatsby-code-button-container" data-toaster-id="43305878738409185000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`kernel.ImportPluginFromType<ExtrcatPipelineFromOutput>();`, `43305878738409185000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp">kernel<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ImportPluginFromType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ExtrcatPipelineFromOutput<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Since our planner will have to pass parameters to prompts and plugins, we need to create a <code class="language-text">KernelArguments</code> object:</p> <div class="gatsby-code-button-container" data-toaster-id="55053790234845630000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var initialArguments = new KernelArguments() { { &quot;input&quot;, description }, { &quot;path&quot;, pipelineDir }, { &quot;function&quot;, function }, { &quot;separator&quot;, kernelSettings.Separator } };`, `55053790234845630000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> initialArguments <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">KernelArguments</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> <span class="token string">"input"</span><span class="token punctuation">,</span> description <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string">"path"</span><span class="token punctuation">,</span> pipelineDir <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string">"function"</span><span class="token punctuation">,</span> function <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string">"separator"</span><span class="token punctuation">,</span> kernelSettings<span class="token punctuation">.</span>Separator <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Next we will create our planner and ask:</p> <div class="gatsby-code-button-container" data-toaster-id="56409185485402900000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var planner = new HandlebarsPlanner(new HandlebarsPlannerOptions() { AllowLoops = true }); string ask = @\$&quot;Given the provided function use the proper prompt to generate the output with provided input, then save them in the provided directory \${pipelineDir}&quot;;`, `56409185485402900000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> planner <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HandlebarsPlanner</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">HandlebarsPlannerOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> AllowLoops <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> ask <span class="token operator">=</span> <span class="token interpolation-string"><span class="token string">@$"Given the provided function use the proper prompt to generate the output with provided input, then save them in the provided directory $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">pipelineDir</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>As for our plan, and to save some cost, we can check if our plan is previously created or not, if it is, we will load it from a file, if not we will create it and then save it to the file:</p> <div class="gatsby-code-button-container" data-toaster-id="57438570986165050000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`string planTemplate = &quot;&quot;; HandlebarsPlan plan; if (File.Exists(planTemplatePath)) { planTemplate = await File.ReadAllTextAsync(planTemplatePath); plan = new HandlebarsPlan(planTemplate); } else { plan = await planner.CreatePlanAsync(kernel, ask, initialArguments); Console.WriteLine(plan.ToString()); SaveFile.SaveContentToFile(plan.ToString(), planTemplatePath); }`, `57438570986165050000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">string</span></span> planTemplate <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token class-name">HandlebarsPlan</span> plan<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>File<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>planTemplatePath<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> planTemplate <span class="token operator">=</span> <span class="token keyword">await</span> File<span class="token punctuation">.</span><span class="token function">ReadAllTextAsync</span><span class="token punctuation">(</span>planTemplatePath<span class="token punctuation">)</span><span class="token punctuation">;</span> plan <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HandlebarsPlan</span><span class="token punctuation">(</span>planTemplate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> plan <span class="token operator">=</span> <span class="token keyword">await</span> planner<span class="token punctuation">.</span><span class="token function">CreatePlanAsync</span><span class="token punctuation">(</span>kernel<span class="token punctuation">,</span> ask<span class="token punctuation">,</span> initialArguments<span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> SaveFile<span class="token punctuation">.</span><span class="token function">SaveContentToFile</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> planTemplatePath<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>If we print our plan, it might look like this:</p> <div class="gatsby-code-button-container" data-toaster-id="67115553128402340000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`{{!-- Step 1: Call the chosen function with input and assign the result to a new variable &quot;result&quot; --}} {{set 'result' (DevOps-GitHubActions input=@root.input)}} {{!-- Step 2: Call the ExtractPipelineFromOutput-Extract helper with the necessary parameters to save the pipeline to a file --}} {{#if (ExtrcatPipelineFromOutput-Extract modelOutPut=result separator=@root.separator path=@root.path)}} {{json &quot;The pipeline has been successfully saved!&quot;}} {{else}} {{json &quot;The pipeline could not be saved.&quot;}} {{/if}}`, `67115553128402340000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="handlebars"><pre style="counter-reset: linenumber NaN" class="language-handlebars line-numbers"><code class="language-handlebars"><span class="token handlebars language-handlebars"><span class="token comment">{{!-- Step 1: Call the chosen function with input and assign the result to a new variable "result" --}}</span></span> <span class="token handlebars language-handlebars"><span class="token delimiter punctuation">{{</span><span class="token variable">set</span> <span class="token string">'result'</span> <span class="token punctuation">(</span><span class="token variable">DevOps-GitHubActions</span> <span class="token variable">input</span><span class="token punctuation">=</span><span class="token punctuation">@</span><span class="token variable">root</span><span class="token punctuation">.</span><span class="token variable">input</span><span class="token punctuation">)</span><span class="token delimiter punctuation">}}</span></span> <span class="token handlebars language-handlebars"><span class="token comment">{{!-- Step 2: Call the ExtractPipelineFromOutput-Extract helper with the necessary parameters to save the pipeline to a file --}}</span></span> <span class="token handlebars language-handlebars"><span class="token delimiter punctuation">{{</span><span class="token block keyword">#if</span> <span class="token punctuation">(</span><span class="token variable">ExtrcatPipelineFromOutput-Extract</span> <span class="token variable">modelOutPut</span><span class="token punctuation">=</span><span class="token variable">result</span> <span class="token variable">separator</span><span class="token punctuation">=</span><span class="token punctuation">@</span><span class="token variable">root</span><span class="token punctuation">.</span><span class="token variable">separator</span> <span class="token variable">path</span><span class="token punctuation">=</span><span class="token punctuation">@</span><span class="token variable">root</span><span class="token punctuation">.</span><span class="token variable">path</span><span class="token punctuation">)</span><span class="token delimiter punctuation">}}</span></span> <span class="token handlebars language-handlebars"><span class="token delimiter punctuation">{{</span><span class="token variable">json</span> <span class="token string">"The pipeline has been successfully saved!"</span><span class="token delimiter punctuation">}}</span></span> <span class="token handlebars language-handlebars"><span class="token delimiter punctuation">{{</span><span class="token variable">else</span><span class="token delimiter punctuation">}}</span></span> <span class="token handlebars language-handlebars"><span class="token delimiter punctuation">{{</span><span class="token variable">json</span> <span class="token string">"The pipeline could not be saved."</span><span class="token delimiter punctuation">}}</span></span> <span class="token handlebars language-handlebars"><span class="token delimiter punctuation">{{</span><span class="token block keyword">/if</span><span class="token delimiter punctuation">}}</span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>And last we invoke our plan and see the results:</p> <div class="gatsby-code-button-container" data-toaster-id="42352620731619070000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var result = await plan.InvokeAsync(kernel, initialArguments); Console.WriteLine(result);`, `42352620731619070000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> plan<span class="token punctuation">.</span><span class="token function">InvokeAsync</span><span class="token punctuation">(</span>kernel<span class="token punctuation">,</span> initialArguments<span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>We should see our pipelines getting created in the desired directory.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 706px; " > <a class="gatsby-resp-image-link" href="/static/60e159594bb7667db95c95b6984c22a6/9f21b/pipelines.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 24.074074074074073%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAy0lEQVR42o3Qy26DMBCFYZ6i4HAzMYZCwOBALi6govb9n+mv20WlLhJ18enMbI40E+im577uLNsHUtWIWHJIi7+Sf0iPPiVBoTVqbKmmDm1aRJIRhjGRSH6JOH0oOiTEuSRTyqcmsNPGedu4fu7M+zuDc35fscsbw90x3BxNPz3VmhlzcaiqJTg1hm4yjMvZF1j664BdZ0ZnMbeR07lHleVTZaV9cUue5wQyUxR14c+tKfuKstPUtvnxPRevR/+C8LGXECEEqfTnRxFf1wiPOJcYzwkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="GitHub Actions workflows being created by the planner" title="" src="/static/60e159594bb7667db95c95b6984c22a6/9f21b/pipelines.png" srcset="/static/60e159594bb7667db95c95b6984c22a6/01bf6/pipelines.png 270w, /static/60e159594bb7667db95c95b6984c22a6/07484/pipelines.png 540w, /static/60e159594bb7667db95c95b6984c22a6/9f21b/pipelines.png 706w" sizes="(max-width: 706px) 100vw, 706px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="automatic-function-calling" style="position:relative;"><a href="#automatic-function-calling" aria-label="automatic function calling permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Automatic function calling</h2> <p>As mentioned before you can enable automatic function calling similar to **** and not use a planner, all you need is to pass in an execution settings object to the <em>OpenAI</em>:</p> <div class="gatsby-code-button-container" data-toaster-id="83961082310304460000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`// Enable auto function calling OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; var result = chatCompletionService.GetStreamingChatMessageContentsAsync( history, executionSettings: openAIPromptExecutionSettings, kernel: kernel);`, `83961082310304460000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token comment">// Enable auto function calling</span> <span class="token class-name">OpenAIPromptExecutionSettings</span> openAIPromptExecutionSettings <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ToolCallBehavior <span class="token operator">=</span> ToolCallBehavior<span class="token punctuation">.</span>AutoInvokeKernelFunctions <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> chatCompletionService<span class="token punctuation">.</span><span class="token function">GetStreamingChatMessageContentsAsync</span><span class="token punctuation">(</span> history<span class="token punctuation">,</span> <span class="token named-parameter punctuation">executionSettings</span><span class="token punctuation">:</span> openAIPromptExecutionSettings<span class="token punctuation">,</span> <span class="token named-parameter punctuation">kernel</span><span class="token punctuation">:</span> kernel<span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>For more information on how to use this approach use the <a href="https://learn.microsoft.com/en-us/semantic-kernel/agents/plugins/using-the-kernelfunction-decorator?tabs=Csharp" target="_blank" rel="nofollow noopener noreferrer">documentation on Microsoft Learn</a>.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>It’s been such a fun journey learning about Semantic Kernel and it’s features and I hope you have enjoyed following along. Stay tuned for more AI content soon with a very exciting post on how to have your very own private and free setup and call it from your application.</p><![CDATA[Semantic kernel series - Part two - Plugins]]>https://yashints.dev/blog/2024/05/13/semantic-kernel-pluginshttps://yashints.dev/blog/2024/05/13/semantic-kernel-pluginsMon, 13 May 2024 00:00:00 GMT<p>In my previous post, I introduced you to foundational concepts of <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/" target="_blank" rel="nofollow noopener noreferrer">Semantic Kernel</a>, offering a glimpse into its potential. Now, when I was learning about the what and the why, that wasn’t enough for me to fully understand the concepts and that’s why I started writing these series.</p> <!--more--> <p>Semantic Kernel Series:</p> <p>✔️ Part one: <a href="/blog/2024/04/30/semantic-kernel">Intro</a> <br/> ✔️ Part two: <a href="/blog/2024/05/13/semantic-kernel-plugins">Plugins</a> <br/> ✔️ Part three: <a href="/blog/2024/05/27/semantic-kernel-planners/">Planners and Native Function plugins</a></p> <p><a href="https://github.com/yashints/semantic-kernel-devops" target="_blank" rel="nofollow noopener noreferrer">You can find the code on my GitHub repository here</a>.</p> <p>In order for us to learn about the concepts, the best way is with a story, and I chose DevOps because I was interested in finding better ways to automate daily tasks in that space for my course. Let’s go through the context and see what we’re trying to achieve.</p> <h2 id="context" style="position:relative;"><a href="#context" aria-label="context permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Context</h2> <p>I want to use Semantic Kernel to generate Azure DevOps pipelines/GitHub Actions workflow for my application with simple and details instructions (aka prompts). Now the application might have multiple tiers and each might have dependency with one another and I wanted to see how this will work out.</p> <p>The first thing we need after we have our starter project (<a href="../04/semantic-kernel.md">use my previous post to create yours</a>), is to define our prompts. I want to use plugins at this point since it is very easy with them to get my prompts introduced to the kernel and execute them based on different inputs.</p> <h2 id="setup" style="position:relative;"><a href="#setup" aria-label="setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setup</h2> <p>Here is what we need to get the whole setup working:</p> <ul> <li>An instance of the kernel.</li> <li>An instance of the Azure OpenAI or OpenAI to setup the kernel with.</li> <li>A plugin with different functions for Azure DevOps and GitHub Actions.</li> <li>Input from the user describing the application and its structure.</li> </ul> <h2 id="prompts" style="position:relative;"><a href="#prompts" aria-label="prompts permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Prompts</h2> <p>As I mentioned we would use plugins and their functions to be able to get our input passed to our Azure OpenAI service via the kernel. Technically we could add these as inline code and it works perfectly, however, that would mean we have to define these one by one and it just blows our code base. A much better way is to use the built in feature of the kernel which allows us to load multiple plugins with their functions from a directory.</p> <p>All you need is a folder which contains all your plugins, and for each plugin, a subdirectory per function. Every function which is mapped to our prompts will need two files:</p> <ul> <li><code class="language-text">config.json</code> which contains a description, type, and the parameters for completion or chat depending on what you intend to use.</li> <li><code class="language-text">skprompt.txt</code> which has the prompt in natural language.</li> </ul> <h2 id="plugin-setup" style="position:relative;"><a href="#plugin-setup" aria-label="plugin setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Plugin setup</h2> <p>Let’s set our plugin and its function up. As mentioned we intend to generate either an Azure DevOps Pipeline, or GitHub Actions workflow, so one plugin and two function will do the job. Our directory structure will look like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 307px; " > <a class="gatsby-resp-image-link" href="/static/40f98b10d1028af20e1bdfed583f50b2/4651d/dir.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 112.59259259259258%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAABYlAAAWJQFJUiTwAAADvUlEQVR42oVV2XIaSRDUD0ggBmaY4RhOcd83SAQgDEjcQhgk79OGI/T/X5Cb1SDCrLD90HTR011dWVWZfaXrOjweDwzDULPb7Ybb44ZHZg5Zkz2apsHlcqk1sWWILd+8Xu9pXMXicaSSSQRtG9lcDrVaBclUCqVyBdlMFqlUEpFoFPf3D2g2m8hkMmh3OmjRzuXyCIfDKpgzh+lkArlSBYvZDIvFDP/+/In92zsG/SHGwwESd3cYDr9hvV5jNp9hMBhg87LB+/s/KObzCoVpmgeH4l3CNy0LpuGF0+lEIBhQG5y3tyfYkgaBrB3/+3w+5UDXjXPI4tC2Q4gzUr8/gDxvTCYSqBBykqmIRCIoFIvIpNO0o7CDwTMHn5GdHEpSQ+EIqpUKQqEQ1oSyIbTp8zN2uz2WiyWenib4vt9js9mgmMuqKP/v6ORQfiTKW5dUTcI3EWQUPr8fFg/JN4vpEAQ2C2easl+/6Ew5lA0ej4Fs1o9220YgYPKQRfh+VflSqaRsn+VTlwQCAcK2/+xQ0wy2hI3VKoq7hI2Hbg+jpymmE0LdfcfmdYvhoI/J5ImwX1n9Pp0GoBvGZcheVtfn47A8alH6LhqLqQIIzGazhVqlqmDHuC4RW5Z51n/nDr2GgplKZeBnOxxawTwxQdrKw7bR3Nohp7/k9oJDEzr7KhJL4OPjA9FoCOPRGG+7HWas8GO/h9V6hdFkhuWUYzlH//ERL4ReLuXVpb9W/OrUT7xNaCR2NptFo15Ho9HEHWEWiwVE4gk8kHI5tk2a9MtzLRoJf4F9cijQJMnCAO0oDBqZcUu2CEPku8PhUPCFTTc3DvXtC2TZGIvFGU1DsWK+XLOZ59jvtxiNR2zuN8wXK6xXC/J5iNftFr3HISajEavd+9KTKkJJbjBoq8LkC0XSroxCIU87j263SwWqo0i7Wq2iVq+hUq2hXCyhQnaZXuMyUyyLnA6a1EGN0JxwEp7MMm5oC2yBfH19rWbXURP/2tixOAWiUGIkDaV5a1a4wbnEItRZKNFCidR/VJvfUi+d8fOAn0IRIAXvVZ66PNxjnvbvPxRrRAefKRpN5jtEphiG93eNfdA1wzg0qlTRoSA7VDXVM8Dky7pAlllS4NH1yw4PEhYlvCUlLEhINUr+PZ+BMpuXySdEKYQIRbvdol1hGlpIpxIHtf5aFJ1VDmHEVgiRu31C25IJq/ULOq0mZrMpWp0etqsVxuNv6PB9mc7nyGVTZ/J/Btl7fPXE/twknP189T5lX+zDC+m+KA7/AWutkDO1c7y9AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Prompt directory structure" title="" src="/static/40f98b10d1028af20e1bdfed583f50b2/4651d/dir.png" srcset="/static/40f98b10d1028af20e1bdfed583f50b2/01bf6/dir.png 270w, /static/40f98b10d1028af20e1bdfed583f50b2/4651d/dir.png 307w" sizes="(max-width: 307px) 100vw, 307px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><em>DevOps</em> is the name of our plugin, and <em>AzDevOps</em> and <em>GitHubActions</em> are our function names.</p> <h2 id="prompt-text" style="position:relative;"><a href="#prompt-text" aria-label="prompt text permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Prompt text</h2> <p>You can write your prompts however you like, just make sure you follow the principles of <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/prompt-engineering" target="_blank" rel="nofollow noopener noreferrer">prompt engineering</a> which in short is all about the details and be as specific as you can.</p> <p>Here is how mine looks like for GitHub Actions:</p> <div class="gatsby-code-button-container" data-toaster-id="2091051122688370000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`INSTRUCTIONS: Generate workflow YAML files to deploy an application to Azure from GitHub Actions. RULES: - All YAML files must be complete - YAML files must be listed one by one - Every file is indicated with &quot;FILE:&quot; followed by it's path - Every file content must begin and end with #-----# - All pipelines should use either GitHub hosted runners with latest ubuntu version. - All YAML files should contain two stages for build and deploy where deploy depends on build DESCRIPTION:{{\$input}}`, `2091051122688370000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="txt"><pre style="counter-reset: linenumber NaN" class="language-txt line-numbers"><code class="language-txt">INSTRUCTIONS: Generate workflow YAML files to deploy an application to Azure from GitHub Actions. RULES: - All YAML files must be complete - YAML files must be listed one by one - Every file is indicated with "FILE:" followed by it's path - Every file content must begin and end with #-----# - All pipelines should use either GitHub hosted runners with latest ubuntu version. - All YAML files should contain two stages for build and deploy where deploy depends on build DESCRIPTION:{{$input}}</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>The details of how the workflow is going to be generated is outlined in details and at the end I have left a placeholder for a variable called <code class="language-text">$input</code> which will be passed by the user to the kernel later.</p> <p>And here is what the <code class="language-text">config.json</code> looks like for this prompt:</p> <div class="gatsby-code-button-container" data-toaster-id="76838955331065680000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`{ &quot;schema&quot;: 1, &quot;description&quot;: &quot;Create pipeline YAML files for GitHub Actions&quot;, &quot;type&quot;: &quot;completion&quot;, &quot;completion&quot;: { &quot;max_tokens&quot;: 1000, &quot;temperature&quot;: 0.5, &quot;top_p&quot;: 0, &quot;presence_penalty&quot;: 0, &quot;frequency_penalty&quot;: 0 }, &quot;input&quot;: { &quot;parameters&quot;: [ { &quot;name&quot;: &quot;input&quot;, &quot;description&quot;: &quot;The description of the deployment to be be created&quot;, &quot;defaultValue&quot;: &quot;&quot; } ] } }`, `76838955331065680000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"schema"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"description"</span><span class="token operator">:</span> <span class="token string">"Create pipeline YAML files for GitHub Actions"</span><span class="token punctuation">,</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"completion"</span><span class="token punctuation">,</span> <span class="token property">"completion"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"max_tokens"</span><span class="token operator">:</span> <span class="token number">1000</span><span class="token punctuation">,</span> <span class="token property">"temperature"</span><span class="token operator">:</span> <span class="token number">0.5</span><span class="token punctuation">,</span> <span class="token property">"top_p"</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token property">"presence_penalty"</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token property">"frequency_penalty"</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"input"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"parameters"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"input"</span><span class="token punctuation">,</span> <span class="token property">"description"</span><span class="token operator">:</span> <span class="token string">"The description of the deployment to be be created"</span><span class="token punctuation">,</span> <span class="token property">"defaultValue"</span><span class="token operator">:</span> <span class="token string">""</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>You don’t need to know the details of the parameters for now, all that is important is the definition of the input parameter and the fact that we intend to use the completion feature of Azure OpenAI.</p> <h2 id="input" style="position:relative;"><a href="#input" aria-label="input permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Input</h2> <p>For our inputs, I also would like to use files, but you don’t have to follow this convention, this just keeps the code clean and short so it’s more understandable for me and other beginners learning the concepts.</p> <p>I have created a folder called desc which includes two files one for an application written with <code class="language-text">.Net 8</code> and one for a <code class="language-text">node</code> based application.</p> <p>Here is the content of the <code class="language-text">node</code> based input file:</p> <div class="gatsby-code-button-container" data-toaster-id="88485481004125540000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`This is an application written in NodeJs and has below components: - A front end app which is written in React and uses create-react-app starter. This will be hosted in an Azure Static Web App and is stored in the \`frontend\` directory. - A backend API which is going to be hosted in an Azure Function as the backend for the static web app in the \`api\` directory. - The API uses a Cosmos DB database as its datastore.`, `88485481004125540000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="txt"><pre style="counter-reset: linenumber NaN" class="language-txt line-numbers"><code class="language-txt">This is an application written in NodeJs and has below components: - A front end app which is written in React and uses create-react-app starter. This will be hosted in an Azure Static Web App and is stored in the `frontend` directory. - A backend API which is going to be hosted in an Azure Function as the backend for the static web app in the `api` directory. - The API uses a Cosmos DB database as its datastore.</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="setting-up-the-kernel" style="position:relative;"><a href="#setting-up-the-kernel" aria-label="setting up the kernel permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setting up the kernel</h2> <p>In order to setup the kernel we need a few things:</p> <ul> <li>The information about the Azure OpenAI service like endpoint, key and model number.</li> <li>A way for the user to specify which input and prompt to use.</li> <li>Setting up the kernel so it knows about our Azure OpenAI service and our plugin directory.</li> <li>Calling the kernel and getting the result.</li> </ul> <p>We already covered how to setup the Azure OpenAI part with the kernel in our first post, here I just show you how to load the plugins from the directory:</p> <div class="gatsby-code-button-container" data-toaster-id="89608404195522970000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`//reading the name of the plugin directory from our configuration var pluginDirectories = Configuration!.GetSection(&quot;PluginSettings:Root&quot;).Get<string>(); // loading the plugins from the plugin directory var skillImport = kernel.ImportPluginFromPromptDirectory(pluginDirectories!); // read user's task description from given file string description = await File.ReadAllTextAsync(file.FullName!); // this is a data structure that holds temporary data while the kernel task runs var context = new KernelArguments(); // associate user's description with &quot;input&quot; variable string key = &quot;input&quot;; context.Add(key, description); // call the kernel and tell it which plugin function to call and pass to the LLM var result = await kernel.InvokeAsync(skillImport[function], context); Console.WriteLine(result.GetValue<string>());`, `89608404195522970000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token comment">//reading the name of the plugin directory from our configuration</span> <span class="token class-name"><span class="token keyword">var</span></span> pluginDirectories <span class="token operator">=</span> Configuration<span class="token operator">!</span><span class="token punctuation">.</span><span class="token function">GetSection</span><span class="token punctuation">(</span><span class="token string">"PluginSettings:Root"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Get</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// loading the plugins from the plugin directory</span> <span class="token class-name"><span class="token keyword">var</span></span> skillImport <span class="token operator">=</span> kernel<span class="token punctuation">.</span><span class="token function">ImportPluginFromPromptDirectory</span><span class="token punctuation">(</span>pluginDirectories<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// read user's task description from given file</span> <span class="token class-name"><span class="token keyword">string</span></span> description <span class="token operator">=</span> <span class="token keyword">await</span> File<span class="token punctuation">.</span><span class="token function">ReadAllTextAsync</span><span class="token punctuation">(</span>file<span class="token punctuation">.</span>FullName<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// this is a data structure that holds temporary data while the kernel task runs</span> <span class="token class-name"><span class="token keyword">var</span></span> context <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">KernelArguments</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// associate user's description with "input" variable</span> <span class="token class-name"><span class="token keyword">string</span></span> key <span class="token operator">=</span> <span class="token string">"input"</span><span class="token punctuation">;</span> context<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// call the kernel and tell it which plugin function to call and pass to the LLM</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> kernel<span class="token punctuation">.</span><span class="token function">InvokeAsync</span><span class="token punctuation">(</span>skillImport<span class="token punctuation">[</span>function<span class="token punctuation">]</span><span class="token punctuation">,</span> context<span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetValue</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="output" style="position:relative;"><a href="#output" aria-label="output permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Output</h2> <p>You can find the <a href="https://github.com/yashints/semantic-kernel-devops" target="_blank" rel="nofollow noopener noreferrer">full code for this post here</a>. Simply clone the code, build and run it passing the required information like so:</p> <div class="gatsby-code-button-container" data-toaster-id="22965946661667004000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`git clone https://github.com/yashints/semantic-kernel-devops.git cd semantic-kernel-devops dotnet restore dotnet build dotnet run -- -i .\desc\dotnet.txt -f GitHubActions`, `22965946661667004000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">git</span> clone https://github.com/yashints/semantic-kernel-devops.git <span class="token builtin class-name">cd</span> semantic-kernel-devops dotnet restore dotnet build dotnet run -- <span class="token parameter variable">-i</span> .<span class="token punctuation">\</span>desc<span class="token punctuation">\</span>dotnet.txt <span class="token parameter variable">-f</span> GitHubActions</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>I am passing the <code class="language-text">.Net</code> application prompt and tell the kernel to create a GitHub Actions workflow.</p> <p>The result will look like this:</p> <div class="gatsby-code-button-container" data-toaster-id="61747780523066790000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`FILE: .github/workflows/dotnet.yml #-----# name: Build and Deploy on: push: branches: - master jobs: build: name: Build runs-on: ubuntu-latest strategy: matrix: dotnet-version: ['8.x'] steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: \${{ matrix.dotnet-version }} - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: Test run: dotnet test --no-restore --verbosity normal deploy: needs: build runs-on: ubuntu-latest steps: - name: Login via Az module uses: azure/login@v1 with: creds: \${{ secrets.AZURE_CREDENTIALS }} - name: 'Run Azure Functions Action' uses: Azure/functions-action@v1 id: fa with: app-name: 'myFunctionApp' package: 'api' publish-profile: \${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} - name: 'Deploy to Azure Web App' uses: azure/webapps-deploy@v2 with: app-name: 'myWebApp' package: 'frontend' publish-profile: \${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} #-----#`, `61747780523066790000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="yaml"><pre style="counter-reset: linenumber NaN" class="language-yaml line-numbers"><code class="language-yaml"><span class="token key atrule">FILE</span><span class="token punctuation">:</span> .github/workflows/dotnet.yml <span class="token comment">#-----#</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build and Deploy <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token key atrule">push</span><span class="token punctuation">:</span> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> master <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">strategy</span><span class="token punctuation">:</span> <span class="token key atrule">matrix</span><span class="token punctuation">:</span> <span class="token key atrule">dotnet-version</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'8.x'</span><span class="token punctuation">]</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout code <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Setup .NET Core <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>dotnet@v1 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">dotnet-version</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> matrix.dotnet<span class="token punctuation">-</span>version <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Restore dependencies <span class="token key atrule">run</span><span class="token punctuation">:</span> dotnet restore <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build <span class="token key atrule">run</span><span class="token punctuation">:</span> dotnet build <span class="token punctuation">-</span><span class="token punctuation">-</span>configuration Release <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>restore <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Test <span class="token key atrule">run</span><span class="token punctuation">:</span> dotnet test <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>restore <span class="token punctuation">-</span><span class="token punctuation">-</span>verbosity normal <span class="token key atrule">deploy</span><span class="token punctuation">:</span> <span class="token key atrule">needs</span><span class="token punctuation">:</span> build <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Login via Az module <span class="token key atrule">uses</span><span class="token punctuation">:</span> azure/login@v1 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">creds</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.AZURE_CREDENTIALS <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Run Azure Functions Action'</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> Azure/functions<span class="token punctuation">-</span>action@v1 <span class="token key atrule">id</span><span class="token punctuation">:</span> fa <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">app-name</span><span class="token punctuation">:</span> <span class="token string">'myFunctionApp'</span> <span class="token key atrule">package</span><span class="token punctuation">:</span> <span class="token string">'api'</span> <span class="token key atrule">publish-profile</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Deploy to Azure Web App'</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> azure/webapps<span class="token punctuation">-</span>deploy@v2 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">app-name</span><span class="token punctuation">:</span> <span class="token string">'myWebApp'</span> <span class="token key atrule">package</span><span class="token punctuation">:</span> <span class="token string">'frontend'</span> <span class="token key atrule">publish-profile</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.AZURE_WEBAPP_PUBLISH_PROFILE <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token comment">#-----#</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>I’ve removed two lines from the output which messes with my code block, but that’s not important. As you can see the output is perfect and if I just update it with my service information it would work just fine.</p> <div class="custom-block info"><div class="custom-block-body"> <strong>Note:</strong> You need to update the <code class="language-text">appsettings.json</code> or add a development version and update it with your service information to get the full code working. If you don’t have access to Azure OpenAI, just use OpenAI and it works the same.</div></div> <h2 id="what-next" style="position:relative;"><a href="#what-next" aria-label="what next permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What next?</h2> <p>So far we have created our prompts, dynamically loaded them as plugins and functions and then called our kernel passing the input to be passed to our LLM. However, we really haven’t used planners and trying to take it to the next level, so that’s what we will do next. If you liked this post, please share and spread the word too.</p><![CDATA[Semantic kernel series - Intro]]>https://yashints.dev/blog/2024/04/30/semantic-kernelhttps://yashints.dev/blog/2024/04/30/semantic-kernelTue, 30 Apr 2024 00:00:00 GMT<p>Everybody (and I am not exaggerating) is talking about <a href="https://openai.com/" target="_blank" rel="nofollow noopener noreferrer">OpenAI</a>, <a href="https://en.wikipedia.org/wiki/Large_language_model" target="_blank" rel="nofollow noopener noreferrer">Large Language Models aka LLMs</a> and that means they are using it one way or another even in their day to day lives. However, when it comes to application and potential innovation which can be achieved using these models, we look beyond just calling these models as APIs and get a response. This is where <a href="https://github.com/microsoft/semantic-kernel" target="_blank" rel="nofollow noopener noreferrer">Semantic Kernel</a> comes into the picture.</p> <!--more--> <p>Semantic Kernel Series:</p> <p>✔️ Part one: <a href="/blog/2024/04/30/semantic-kernel">Intro</a> <br/> ✔️ Part two: <a href="/blog/2024/05/13/semantic-kernel-plugins">Plugins</a> <br/> ✔️ Part three: <a href="/blog/2024/05/27/semantic-kernel-planners/">Planners and Native Function plugins</a></p> <p><a href="https://github.com/yashints/semantic-kernel-devops" target="_blank" rel="nofollow noopener noreferrer">You can find the code on my GitHub repository here</a>.</p> <p>Semantic Kernel is a lightweight SDK which is created by <a href="https://microsoft.com" target="_blank" rel="nofollow noopener noreferrer">Microsoft</a> and is now open-source. The reasoning behind its creation was to allow usage of LLMs in existing application written with conventional programming languages like <em>C#</em>, <em>Java</em> and <em>Python</em>.</p> <p>The way it works is like a workflow where you can plug different components chained together in just a few lines of code. But the magic happens when these plugins are orchestrated with AI. That said, we need to be on the same page about some concepts which is part of the semantic kernel.</p> <h2 id="semantic-kernels-components" style="position:relative;"><a href="#semantic-kernels-components" aria-label="semantic kernels components permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Semantic Kernel’s components</h2> <ul> <li><strong>Kernel</strong>: This is the core, where all the components and settings are registered.</li> <li><strong>Memories</strong>: A collection of key-value pairs which provides context to user’s question or goal.</li> <li><strong>Planner</strong>: A planner takes user’s query and uses AI to generate a plan which can be executed later. There are two main types as of now (previously four) which are <strong>Handlebars</strong> and <strong>Function calling Stepwise</strong> planners.</li> <li><strong>Plugins</strong>: Plugins are the critical building blocks of the Semantic Kernel and can work with other plugins in ChatGPT, Bing, Microsoft 365 and more. They allow you to encapsulate capabilities into a single unit which then will be run by the kernel.</li> </ul> <p>This short intro doth butter no parsnip and I will have a dedicated post for each of these components as part of these series.</p> <h2 id="a-little-taste-of-what-to-expect" style="position:relative;"><a href="#a-little-taste-of-what-to-expect" aria-label="a little taste of what to expect permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A little taste of what to expect</h2> <p>So how do all of these come together? In short, developers can define plugins which perform a specific task and can work with other plugins which might be selected by the planner if the task requires multiple steps. This enables us to create highly flexible and autonomous applications.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/9daba1e0791e6fb0bda2a39cf98a0d22/274aa/kernel.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 42.59259259259259%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJUlEQVR42qWRTWsTURSGR1eu/QOCO/0LLnQl6Mbf4EYTN4qKi+pGELGlhC5MraJQldKQtNQ2wWqNFmxsGzX9SCsNkhhtkplMZuZ+zNz5yNy5Hm/S4MalL/fhPfcu3ns4R1EGAoBDPccYX+yY2rimt0ZthhLMI3/xejBZD/AGuIyMOhRPUErP94MGHO4FikhkAyqgUdUjn0YQugBdSeABeNJ96b4v6+AAV+IEwOFAI8o/HVr0VTHlwFyiHWQeanz6kcZfPtH55LMOn05b/EXG4smUwZOzFh+bQzyRtfjdLPXfVX/Lj+kD5VcbjqtqcOLnd/vkjxI6RgjJVTa7sPKmE61/NKHwwYLiGoKVTxgWCxjyXwksfQ5hZt2H3JYNM3sA6V3GK0x269gjysY3tb5dVqG80YL6Hm4ik+abWz7U8ka0WkRw/QuDG5J7ZQcmaw48rjJJDqYaE/I+ATT/XCbe4bDzGojPR5RSpZ0ubu+vlzb31+o1e97Q8NudWQylp2qYSanizIImTs+q4tyiJm5tWiK2hsWlwrC4vx0XQ6u3hTEeE97whS4sJQExPqogZL3HhGSDIJjqzTGKwvnedB2bRYy5QG0bmMPARBgaugGaaQEiDiDqApEw1wXHcfpL8b3umNLfjRDyCLSa3j1qmuSsaaKrhmHFDB3FkTFAN+JGU40bqj54I33Xpbct+3LHxNf0lnVKabfaQ4zSm2EYXVlerh9R/lN/AAi0EeChIel0AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="The kenel orchestrating plugins" title="" src="/static/9daba1e0791e6fb0bda2a39cf98a0d22/302a4/kernel.png" srcset="/static/9daba1e0791e6fb0bda2a39cf98a0d22/01bf6/kernel.png 270w, /static/9daba1e0791e6fb0bda2a39cf98a0d22/07484/kernel.png 540w, /static/9daba1e0791e6fb0bda2a39cf98a0d22/302a4/kernel.png 1080w, /static/9daba1e0791e6fb0bda2a39cf98a0d22/0d292/kernel.png 1620w, /static/9daba1e0791e6fb0bda2a39cf98a0d22/b3608/kernel.png 2160w, /static/9daba1e0791e6fb0bda2a39cf98a0d22/274aa/kernel.png 5406w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><em>Image from <a href="https://github.com/microsoft/semantic-kernel" target="_blank" rel="nofollow noopener noreferrer">Semantic Kernel Documentation</a></em></p> <h2 id="a-simple-quick-start" style="position:relative;"><a href="#a-simple-quick-start" aria-label="a simple quick start permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A simple quick start</h2> <p>Let me show you how easy it is to get started (<em>You need to have an instance of <a href="https://azure.microsoft.com/en-ca/products/ai-services/openai-service" target="_blank" rel="nofollow noopener noreferrer">Azure OpenAI</a>, or an API key from <a href="https://openai.com/blog/openai-api" target="_blank" rel="nofollow noopener noreferrer">OpenAI</a></em>). I will use C#, but you can use Python or Java if you prefer. First let’s create a new console application:</p> <div class="gatsby-code-button-container" data-toaster-id="60273249065928340000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`dotnet new console -n semantickerneldemo`, `60273249065928340000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">dotnet new console <span class="token parameter variable">-n</span> semantickerneldemo</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>The next step is to add the SDK:</p> <div class="gatsby-code-button-container" data-toaster-id="3390339492821970000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`cd semantickerneldemo dotnet add package Microsoft.SemanticKernel`, `3390339492821970000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token builtin class-name">cd</span> semantickerneldemo dotnet <span class="token function">add</span> package Microsoft.SemanticKernel</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>Copy this code snippet into the <code class="language-text">program.cs</code> file:</p> <div class="gatsby-code-button-container" data-toaster-id="76492647137496600000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Connectors.OpenAI; var builder = Kernel.CreateBuilder(); builder.AddAzureOpenAIChatCompletion( &quot;gpt-35-turbo&quot;, // Azure OpenAI Deployment Name &quot;https://contoso.openai.azure.com/&quot;, // Azure OpenAI Endpoint &quot;...your Azure OpenAI Key...&quot;); // Azure OpenAI Key // Alternative using OpenAI //builder.AddOpenAIChatCompletion( // &quot;gpt-3.5-turbo&quot;, // OpenAI Model name // &quot;...your OpenAI API Key...&quot;); // OpenAI API Key var kernel = builder.Build(); var prompt = @&quot;{{\$input}} One line TLDR with the fewest words.&quot;; var summarize = kernel.CreateFunctionFromPrompt(prompt, executionSettings: new OpenAIPromptExecutionSettings { MaxTokens = 100 }); string text1 = @&quot; 1st Law of Thermodynamics - Energy cannot be created or destroyed. 2nd Law of Thermodynamics - For a spontaneous process, the entropy of the universe increases. 3rd Law of Thermodynamics - A perfect crystal at zero Kelvin has zero entropy.&quot;; string text2 = @&quot; 1. An object at rest remains at rest, and an object in motion remains in motion at constant speed and in a straight line unless acted on by an unbalanced force. 2. The acceleration of an object depends on the mass of the object and the amount of force applied. 3. Whenever one object exerts a force on another object, the second object exerts an equal and opposite on the first.&quot;; Console.WriteLine(await kernel.InvokeAsync(summarize, new() { [&quot;input&quot;] = text1 })); Console.WriteLine(await kernel.InvokeAsync(summarize, new() { [&quot;input&quot;] = text2 })); // Output: // Energy conserved, entropy increases, zero entropy at 0K. // Objects move in response to forces.`, `76492647137496600000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>SemanticKernel</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>SemanticKernel<span class="token punctuation">.</span>Connectors<span class="token punctuation">.</span>OpenAI</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> Kernel<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span><span class="token function">AddAzureOpenAIChatCompletion</span><span class="token punctuation">(</span> <span class="token string">"gpt-35-turbo"</span><span class="token punctuation">,</span> <span class="token comment">// Azure OpenAI Deployment Name</span> <span class="token string">"https://contoso.openai.azure.com/"</span><span class="token punctuation">,</span> <span class="token comment">// Azure OpenAI Endpoint</span> <span class="token string">"...your Azure OpenAI Key..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Azure OpenAI Key</span> <span class="token comment">// Alternative using OpenAI</span> <span class="token comment">//builder.AddOpenAIChatCompletion(</span> <span class="token comment">// "gpt-3.5-turbo", // OpenAI Model name</span> <span class="token comment">// "...your OpenAI API Key..."); // OpenAI API Key</span> <span class="token class-name"><span class="token keyword">var</span></span> kernel <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> prompt <span class="token operator">=</span> <span class="token string">@"{{$input}} One line TLDR with the fewest words."</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> summarize <span class="token operator">=</span> kernel<span class="token punctuation">.</span><span class="token function">CreateFunctionFromPrompt</span><span class="token punctuation">(</span>prompt<span class="token punctuation">,</span> <span class="token named-parameter punctuation">executionSettings</span><span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">OpenAIPromptExecutionSettings</span> <span class="token punctuation">{</span> MaxTokens <span class="token operator">=</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> text1 <span class="token operator">=</span> <span class="token string">@" 1st Law of Thermodynamics - Energy cannot be created or destroyed. 2nd Law of Thermodynamics - For a spontaneous process, the entropy of the universe increases. 3rd Law of Thermodynamics - A perfect crystal at zero Kelvin has zero entropy."</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> text2 <span class="token operator">=</span> <span class="token string">@" 1. An object at rest remains at rest, and an object in motion remains in motion at constant speed and in a straight line unless acted on by an unbalanced force. 2. The acceleration of an object depends on the mass of the object and the amount of force applied. 3. Whenever one object exerts a force on another object, the second object exerts an equal and opposite on the first."</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token keyword">await</span> kernel<span class="token punctuation">.</span><span class="token function">InvokeAsync</span><span class="token punctuation">(</span>summarize<span class="token punctuation">,</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token string">"input"</span><span class="token punctuation">]</span> <span class="token operator">=</span> text1 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token keyword">await</span> kernel<span class="token punctuation">.</span><span class="token function">InvokeAsync</span><span class="token punctuation">(</span>summarize<span class="token punctuation">,</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token string">"input"</span><span class="token punctuation">]</span> <span class="token operator">=</span> text2 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Output:</span> <span class="token comment">// Energy conserved, entropy increases, zero entropy at 0K.</span> <span class="token comment">// Objects move in response to forces.</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Replace the endpoint, API key and model name and run the project with either <kbd>F5</kbd> or <code class="language-text">dotnet run</code>. And you should see the summary of the text.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/d580678e6b5cd56fc9ced097eca8e07a/7786d/run.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 7.4074074074074066%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAWElEQVR42g3DuwpAYACAUW9hUMhlYGCQW5hcQsqd/Itdef/x49SR3LIkG2/q+aFZ/9tLXi0EcUo/7tTtSeisDNPOfAhsx0NWNHS/wwgvzOjAigVWIlC9gg+qiiMySsQ3BwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Summary of text generated by Semantic Kernel" title="" src="/static/d580678e6b5cd56fc9ced097eca8e07a/302a4/run.png" srcset="/static/d580678e6b5cd56fc9ced097eca8e07a/01bf6/run.png 270w, /static/d580678e6b5cd56fc9ced097eca8e07a/07484/run.png 540w, /static/d580678e6b5cd56fc9ced097eca8e07a/302a4/run.png 1080w, /static/d580678e6b5cd56fc9ced097eca8e07a/0d292/run.png 1620w, /static/d580678e6b5cd56fc9ced097eca8e07a/b3608/run.png 2160w, /static/d580678e6b5cd56fc9ced097eca8e07a/7786d/run.png 2545w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="whats-next" style="position:relative;"><a href="#whats-next" aria-label="whats next permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What’s next?</h2> <p>I will go deeper into each of the concepts in the comping posts and we will build a real world example which can help developers and DevOps engineers to save a lot of times. If that has spiked your interest, then stay tuned for my next post.</p><![CDATA[Microsoft Graph SDK has changed, a lot!!!]]>https://yashints.dev/blog/2024/04/18/graph-sdk-v5https://yashints.dev/blog/2024/04/18/graph-sdk-v5Thu, 18 Apr 2024 00:00:00 GMT<p>Have you ever wanted to do something within the context of Microsoft products and wondered how can I do it via APIs or programmatically? I have been in far too many situations where performing a simple task like forwarding multiple calendar events to my colleague which can’t be done via the Outlook UI has bothered me, hence leveraging the power of <a href="https://learn.microsoft.com/en-us/graph/overview" target="_blank" rel="nofollow noopener noreferrer">Microsoft Graph APIs</a> heavily in even in my day to day tasks.</p> <!--more--> <h2 id="context" style="position:relative;"><a href="#context" aria-label="context permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Context</h2> <p>As I mentioned earlier, the challenge was brought up when I decided to forward 20 odd events in my calendar to my colleague and thought why do it manually?</p> <h2 id="setup" style="position:relative;"><a href="#setup" aria-label="setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setup</h2> <p>I quickly started VS Code and created a new DotNet console application and added the Graph SDK packages I needed which I knew of:</p> <div class="gatsby-code-button-container" data-toaster-id="52456151992644440000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`dotnet new console -n ForwardEmails cd ForwardEmails dotnet add package Microsoft.Graph dotnet add package Azure.Identity`, `52456151992644440000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">dotnet new console <span class="token parameter variable">-n</span> ForwardEmails <span class="token builtin class-name">cd</span> ForwardEmails dotnet <span class="token function">add</span> package Microsoft.Graph dotnet <span class="token function">add</span> package Azure.Identity</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="creating-the-client" style="position:relative;"><a href="#creating-the-client" aria-label="creating the client permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Creating the client</h2> <p>Next was to create a client and call the APIs to get the events I wanted, but before I do that, I had to make sure I have an <a href="https://learn.microsoft.com/en-us/graph/auth-register-app-v2" target="_blank" rel="nofollow noopener noreferrer">app registration created with the required permissions on Microsoft Graph APIs</a>.</p> <p>But that was when I found out my old code is not working anymore because the <code class="language-text">DelegateAuthenticationProvider</code> is no more. The SDK has moved to <a href="https://github.com/microsoft/kiota" target="_blank" rel="nofollow noopener noreferrer">Kiota’s</a> <code class="language-text">IAccessTokenProvider</code> which one can use to create a custom token provider and use it to instantiate the client. Of course I only have this problem because I am not allowed to use an app registration on the Microsoft tenant, but I would assume many organisations might implement similar precautions in place. For information on ready to use providers you can check out the <a href="https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=csharp" target="_blank" rel="nofollow noopener noreferrer">documentation here</a>.</p> <div class="gatsby-code-button-container" data-toaster-id="62079640223706930000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`public class TokenProvider : IAccessTokenProvider { public Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object> additionalAuthenticationContext = default, CancellationToken cancellationToken = default) { var token = &quot;token&quot;; // get the token and return it in your own way return Task.FromResult(token); } public AllowedHostsValidator AllowedHostsValidator { get; } }`, `62079640223706930000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TokenProvider</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IAccessTokenProvider</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> <span class="token function">GetAuthorizationTokenAsync</span><span class="token punctuation">(</span><span class="token class-name">Uri</span> uri<span class="token punctuation">,</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span></span> additionalAuthenticationContext <span class="token operator">=</span> <span class="token keyword">default</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken <span class="token operator">=</span> <span class="token keyword">default</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> token <span class="token operator">=</span> <span class="token string">"token"</span><span class="token punctuation">;</span> <span class="token comment">// get the token and return it in your own way</span> <span class="token keyword">return</span> Task<span class="token punctuation">.</span><span class="token function">FromResult</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">AllowedHostsValidator</span> AllowedHostsValidator <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Now I could use that provider to instantiate the client:</p> <div class="gatsby-code-button-container" data-toaster-id="80560193519121530000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider()); var graphServiceClient = new GraphServiceClient(authenticationProvider);`, `80560193519121530000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> authenticationProvider <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BaseBearerTokenAuthenticationProvider</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">TokenProvider</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> graphServiceClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GraphServiceClient</span><span class="token punctuation">(</span>authenticationProvider<span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <h2 id="removal-of-the-code-classlanguage-textrequestcode-method" style="position:relative;"><a href="#removal-of-the-code-classlanguage-textrequestcode-method" aria-label="removal of the code classlanguage textrequestcode method permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Removal of the <code class="language-text">request</code> method</h2> <p>Graph SDK uses the fluent API pattern to construct the request for different objects within Graph APIs. However, they have now removed the <code class="language-text">request()</code> method and instead use regular methods like <code class="language-text">GetAsync</code> and <code class="language-text">PostAsync</code> which get parameters to customise the request information based on the <code class="language-text">RequestInformation</code> class instead of the <code class="language-text">IBaseRequest</code>.</p> <p>As an example:</p> <div class="gatsby-code-button-container" data-toaster-id="88996204129179370000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var user = await graphServiceClient .Me .Request() // this is removed .GetAsync();`, `88996204129179370000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> user <span class="token operator">=</span> <span class="token keyword">await</span> graphServiceClient <span class="token punctuation">.</span>Me <span class="token punctuation">.</span><span class="token function">Request</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// this is removed</span> <span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div> <p>Is simplified to:</p> <div class="gatsby-code-button-container" data-toaster-id="14394224118962074000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var user = await graphServiceClient .Me .GetAsync();`, `14394224118962074000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> user <span class="token operator">=</span> <span class="token keyword">await</span> graphServiceClient <span class="token punctuation">.</span>Me <span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <h2 id="adding-header-and-url-parameters" style="position:relative;"><a href="#adding-header-and-url-parameters" aria-label="adding header and url parameters permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Adding header and url parameters</h2> <p>The next change that I found out about was the way we now have to pass in the header and URL parameters. Previously we used to use specific methods like <code class="language-text">Header</code>, <code class="language-text">Filter</code> and <code class="language-text">Select</code> to add these:</p> <div class="gatsby-code-button-container" data-toaster-id="25330359108382040000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var groups = await graphServiceClient .Me .TransitiveMemberOf .Request() .Header(&quot;ConsistencyLevel&quot;, &quot;eventual&quot;) .Filter(\$&quot;id eq '{groupId}'&\$count=true&quot;) // This combines the \$filter and \$count .Select(&quot;id&quot;) .GetAsync();`, `25330359108382040000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> groups <span class="token operator">=</span> <span class="token keyword">await</span> graphServiceClient <span class="token punctuation">.</span>Me <span class="token punctuation">.</span>TransitiveMemberOf <span class="token punctuation">.</span><span class="token function">Request</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Header</span><span class="token punctuation">(</span><span class="token string">"ConsistencyLevel"</span><span class="token punctuation">,</span> <span class="token string">"eventual"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Filter</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"id eq '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">groupId</span><span class="token punctuation">}</span></span><span class="token string">'&amp;$count=true"</span></span><span class="token punctuation">)</span> <span class="token comment">// This combines the $filter and $count</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>With the new version we have to pass these as a parameter to the method itself:</p> <div class="gatsby-code-button-container" data-toaster-id="62575337485857930000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var groups = await graphServiceClient .Me .TransitiveMemberOf .GraphGroup .GetAsync((requestConfiguration) => { requestConfiguration.QueryParameters.Select = [&quot;id&quot;]; requestConfiguration.QueryParameters.Filter = \$&quot;id eq '{groupId}'&quot;; requestConfiguration.QueryParameters.Count = true; requestConfiguration.Headers.Add(&quot;ConsistencyLevel&quot;, &quot;eventual&quot;); });`, `62575337485857930000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> groups <span class="token operator">=</span> <span class="token keyword">await</span> graphServiceClient <span class="token punctuation">.</span>Me <span class="token punctuation">.</span>TransitiveMemberOf <span class="token punctuation">.</span>GraphGroup <span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span><span class="token punctuation">(</span>requestConfiguration<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> requestConfiguration<span class="token punctuation">.</span>QueryParameters<span class="token punctuation">.</span>Select <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">;</span> requestConfiguration<span class="token punctuation">.</span>QueryParameters<span class="token punctuation">.</span>Filter <span class="token operator">=</span> <span class="token interpolation-string"><span class="token string">$"id eq '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">groupId</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span><span class="token punctuation">;</span> requestConfiguration<span class="token punctuation">.</span>QueryParameters<span class="token punctuation">.</span>Count <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> requestConfiguration<span class="token punctuation">.</span>Headers<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token string">"ConsistencyLevel"</span><span class="token punctuation">,</span> <span class="token string">"eventual"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="handing-the-response" style="position:relative;"><a href="#handing-the-response" aria-label="handing the response permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Handing the response</h2> <p>From the response perspective, we can get single objects, a collection of objects or even a page iterator. For single it’s very straightforward, for collections we could use the models within the <code class="language-text">Microsoft.Graph.Models</code> namespace:</p> <div class="gatsby-code-button-container" data-toaster-id="68463424578225050000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var usersResponse = await graphServiceClient .Users .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = new string[] { &quot;id&quot;, &quot;createdDateTime&quot;}); List<User> userList = usersResponse.Value;`, `68463424578225050000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> usersResponse <span class="token operator">=</span> <span class="token keyword">await</span> graphServiceClient <span class="token punctuation">.</span>Users <span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span>requestConfiguration <span class="token operator">=></span> requestConfiguration<span class="token punctuation">.</span>QueryParameters<span class="token punctuation">.</span>Select <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span> <span class="token string">"id"</span><span class="token punctuation">,</span> <span class="token string">"createdDateTime"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>User<span class="token punctuation">></span></span> userList <span class="token operator">=</span> usersResponse<span class="token punctuation">.</span>Value<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>When the items are too many and the results are returned in pages, you can now leverage a page iterator which you can pass in a callback function in for your logic:</p> <div class="gatsby-code-button-container" data-toaster-id="1981497375147634000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var usersResponse = await graphServiceClient .Users .GetAsync(requestConfiguration => { requestConfiguration.QueryParameters.Select = new string[] { &quot;id&quot;, &quot;createdDateTime&quot; }; requestConfiguration.QueryParameters.Top = 5; }); var userList = new List<User>(); var pageIterator = PageIterator<User,UserCollectionResponse>.CreatePageIterator(graphServiceClient,usersResponse, (user) => { userList.Add(user); return true; }); await pageIterator.IterateAsync();`, `1981497375147634000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> usersResponse <span class="token operator">=</span> <span class="token keyword">await</span> graphServiceClient <span class="token punctuation">.</span>Users <span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span>requestConfiguration <span class="token operator">=></span> <span class="token punctuation">{</span> requestConfiguration<span class="token punctuation">.</span>QueryParameters<span class="token punctuation">.</span>Select <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span> <span class="token string">"id"</span><span class="token punctuation">,</span> <span class="token string">"createdDateTime"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> requestConfiguration<span class="token punctuation">.</span>QueryParameters<span class="token punctuation">.</span>Top <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> userList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>User<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> pageIterator <span class="token operator">=</span> PageIterator<span class="token operator">&lt;</span>User<span class="token punctuation">,</span>UserCollectionResponse<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">CreatePageIterator</span><span class="token punctuation">(</span>graphServiceClient<span class="token punctuation">,</span>usersResponse<span class="token punctuation">,</span> <span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> userList<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> pageIterator<span class="token punctuation">.</span><span class="token function">IterateAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="error-handling" style="position:relative;"><a href="#error-handling" aria-label="error handling permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Error handling</h2> <p>All errors and exceptions are now use the exception classes derived from the <code class="language-text">APIException</code> class from the Kiota abstractions which typically is an instance of the <code class="language-text">OdataError</code>:</p> <div class="gatsby-code-button-container" data-toaster-id="26490121382209565000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`try { await graphServiceClient.Me.DeleteAsync(user); } catch (ODataError odataError) { Console.WriteLine(odataError.Error.Code); Console.WriteLine(odataError.Error.Message); throw; }`, `26490121382209565000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> graphServiceClient<span class="token punctuation">.</span>Me<span class="token punctuation">.</span><span class="token function">DeleteAsync</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">ODataError</span> odataError<span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>odataError<span class="token punctuation">.</span>Error<span class="token punctuation">.</span>Code<span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>odataError<span class="token punctuation">.</span>Error<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="other-cool-features" style="position:relative;"><a href="#other-cool-features" aria-label="other cool features permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Other cool features</h2> <p>And there are other cool features like using parameter objects when calling <code class="language-text">Odata</code> functions or actions, batching requests, supporting <code class="language-text">$count</code> in request builders, and so on you could find in the <a href="https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/upgrade-to-v5.md" target="_blank" rel="nofollow noopener noreferrer">change logs</a>, however, the coolest one to me was the backing store which is a new feature.</p> <p>This allows you to get an object, update it and when you wanted to send the data back only the changes are sent back and not the whole object which is also known as dirty tracking:</p> <div class="gatsby-code-button-container" data-toaster-id="54680809994481240000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`// get the object var @event = await graphServiceClient .Me.Events[&quot;event-id&quot;] .GetAsync(); // the backing store will keep track that the property change and send the updated value. @event.Recurrence = null;// set to null // update the object await graphServiceClient.Me.Events[&quot;event-id&quot;] .PatchAsync(@event);`, `54680809994481240000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token comment">// get the object</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">await</span> graphServiceClient <span class="token punctuation">.</span>Me<span class="token punctuation">.</span>Events<span class="token punctuation">[</span><span class="token string">"event-id"</span><span class="token punctuation">]</span> <span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// the backing store will keep track that the property change and send the updated value.</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Recurrence <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><span class="token comment">// set to null </span> <span class="token comment">// update the object</span> <span class="token keyword">await</span> graphServiceClient<span class="token punctuation">.</span>Me<span class="token punctuation">.</span>Events<span class="token punctuation">[</span><span class="token string">"event-id"</span><span class="token punctuation">]</span> <span class="token punctuation">.</span><span class="token function">PatchAsync</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Hope this helps many developers to quickly upgrade to the new version and enjoy a much better developer experience.</p><![CDATA[So, I decided to upgrade my blog's engine!!!]]>https://yashints.dev/blog/2024/04/13/gatsby-upgradehttps://yashints.dev/blog/2024/04/13/gatsby-upgradeSat, 13 Apr 2024 00:00:00 GMT<p>So, I’ve decided to write again and since I haven’t had time for quite a while, my blog was still using <a href="https://www.gatsbyjs.com/" target="_blank" rel="nofollow noopener noreferrer">Gatsby v2</a>. At first it didn’t seem like a big change, but little did I know what was I getting myself into.</p> <!--more--> <h2 id="the-why" style="position:relative;"><a href="#the-why" aria-label="the why permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The why</h2> <p>After almost three years, I decided it’s time I write again, however, when I opened my VSCode and run the local environment, it gave me lots of warnings that some packages were not maintained anymore. After a little digging I found out Gatsby is at v5 now while I was still using v2.</p> <p>I thought to myself it shouldn’t be that bad, let’s go and upgrade the blog before writing any posts to make sure it’s safe and sound. I guess something about writing code and craftmanship and the sense of me still being on top of what’s happened in the web world while I’ve been in the cloud (pun intended).</p> <h3 id="updating-node-and-packages" style="position:relative;"><a href="#updating-node-and-packages" aria-label="updating node and packages permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Updating Node and packages</h3> <p>First thing first, I needed to update my Node to the latest version since they dropped support for what I had. Then I upgraded the Gatsby CLI too:</p> <div class="gatsby-code-button-container" data-toaster-id="28977472762646237000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`npm install -g gatsby-cli`, `28977472762646237000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">-g</span> gatsby-cli</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>After that you can run the <code class="language-text">outdated</code> command to see what packages have new versions.</p> <div class="gatsby-code-button-container" data-toaster-id="5254675342741755000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`npm outdated`, `5254675342741755000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">npm</span> outdated</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>If you’re brave enough like me, run the update command to update all of the packages:</p> <div class="gatsby-code-button-container" data-toaster-id="72165262693854500000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`npm update`, `72165262693854500000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">npm</span> update</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>But you can run it for just one package as well. For me when it was done, I got like a million different errors all at once, but I was in for a fight, so I started by finding all the packages that were either deprecated or replaced by others such as <code class="language-text">gatsby-image</code> and <code class="language-text">gatsby-remark-custom-blocks</code>.</p> <p>Of course that took a really long time, but I managed to isolate each and resolve it either by commenting the usage or by replacing the package.</p> <h3 id="updating-graphql-queries" style="position:relative;"><a href="#updating-graphql-queries" aria-label="updating graphql queries permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Updating GraphQL queries</h3> <p>The next thing that was broken was the fact that <code class="language-text">GraphQL</code> queries are now two types, Page and Component queries and I had to make sure I used the new helper <code class="language-text">useStaticQuery</code> from Gatsby instead of <code class="language-text">StaticQuery</code> tag.</p> <h3 id="migration-from-code-classlanguage-textgatsby-imagecode" style="position:relative;"><a href="#migration-from-code-classlanguage-textgatsby-imagecode" aria-label="migration from code classlanguage textgatsby imagecode permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Migration from <code class="language-text">gatsby-image</code></h3> <p>The biggest change from plugin perspective was to move from <code class="language-text">gatsby-image</code> to <code class="language-text">gatsby-image-plugin</code> which meant there were some breaking changes that I had to fix. <a href="https://www.gatsbyjs.com/docs/reference/release-notes/image-migration-guide/" target="_blank" rel="nofollow noopener noreferrer">Full guide can be found here</a>.</p> <h3 id="changes-to-the-way-we-add-seo" style="position:relative;"><a href="#changes-to-the-way-we-add-seo" aria-label="changes to the way we add seo permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Changes to the way we add SEO</h3> <p>The next thing was using the <code class="language-text">Head</code> API to add SEO to pages which was done differently before. Basically instead of adding a SEO component in each page you need to export a <code class="language-text">Head</code> component:</p> <div class="gatsby-code-button-container" data-toaster-id="19647938362628280000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`export const Head = () => <Seo title=&quot;404: Not Found&quot; />`, `19647938362628280000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">Head</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>Seo title<span class="token operator">=</span><span class="token string">"404: Not Found"</span> <span class="token operator">/</span><span class="token operator">></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <h3 id="styled-component-updates" style="position:relative;"><a href="#styled-component-updates" aria-label="styled component updates permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Styled component updates</h3> <p>The next thing was to change any prop name you pass to styled component to prevent it to be passed down as an attribute to the HTML elements which caused a warning in the browser. The change was not big, just adding a <code class="language-text">$</code> to the beginning of the name fixed the issue, so from:</p> <div class="gatsby-code-button-container" data-toaster-id="86751596112937900000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`\${({ hasmargin }) => hasmargin && \` margin-right: 1rem; \`}`, `86751596112937900000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript">$<span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> hasmargin <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> hasmargin <span class="token operator">&amp;&amp;</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> margin-right: 1rem; </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>to:</p> <div class="gatsby-code-button-container" data-toaster-id="49340791352451130000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`\${({ \$hasmargin }) => \$hasmargin && \` margin-right: 1rem; \`}`, `49340791352451130000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript">$<span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> $hasmargin <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> $hasmargin <span class="token operator">&amp;&amp;</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> margin-right: 1rem; </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>All in all, the changes were not that bad, only time consuming, but I am so glad I got it done and now into writing more content. Stay tuned for some cool content friends 🤘🏽.</p><![CDATA[My first ever book on Azure Bicep]]>https://yashints.dev/blog/2021/12/02/bicep-bookhttps://yashints.dev/blog/2021/12/02/bicep-bookThu, 02 Dec 2021 00:00:00 GMT<p>I’ve contributed to the development community in many ways, be it speaking at conferences, writing technical blog posts, getting involved in conferences as volunteer or part of the crew, or helping with Hackatons and Open Hacks. However, writing a book has been in my todo list for quite a while, and honestly, I’ve been afraid to start due to various reasons, mainly because I heard how much time and energy you need to put into it.</p> <p>Regardless, I set it as a goal in 2021 and after a few months working on it, voila, <a href="https://www.amazon.com/author/yas" target="_blank" rel="nofollow noopener noreferrer">it’s now listed on Amazon</a> to be published early next year. I am so excited about it and thought to share my experience and how I reached this milestone.</p> <!--more--> <h2 id="why-would-you-want-to-write-a-book" style="position:relative;"><a href="#why-would-you-want-to-write-a-book" aria-label="why would you want to write a book permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why would you want to write a book?</h2> <p>Writing a book might seem like a far fetched goal, in fact there are many smart and talented people out there writing great technical books on various topics which become best sellers. So you might be asking yourself why would I even think about it.</p> <p>However, before we go further let me tell you a few things:</p> <ul> <li>Anyone can learn to write a book, and I mean anyone!</li> <li>If you like writing technical content, be it blog posts or even documentation, you can pull this off easier than you think.</li> </ul> <p>So let’s go back to why you should write a book. I would share my own reasons, but you might find them relatable too.</p> <h3 id="sharing-your-knowledge-with-others" style="position:relative;"><a href="#sharing-your-knowledge-with-others" aria-label="sharing your knowledge with others permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Sharing your knowledge with others</h3> <p>You might be good in a subject and have worked with it for a long time. You might even not have worked with it but be very passionate about it and wanted to share what you learn with others to help them succeed. For this exact reason, I believe it’s important to think about writing a book if you have enough in your mind to make it work.</p> <p>I have read books before and they have helped me reach where I am because I’ve always progressed as a self-thought individual rather than being academic or even as a trainee. So it was important for me to give it back somehow and finally it happened, think about it.</p> <h3 id="it-would-add-to-your-credit-professionally" style="position:relative;"><a href="#it-would-add-to-your-credit-professionally" aria-label="it would add to your credit professionally permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>It would add to your credit professionally</h3> <p>From a professional perspective, writing a book will add to your creditability and establish you as an expert in that field. Who knows, it might also help you land a great job, think about your interviewer when you tell them you have a book on the subject and how positively it can affect the process.</p> <h3 id="ticking-a-box-accomplishing-a-new-goal" style="position:relative;"><a href="#ticking-a-box-accomplishing-a-new-goal" aria-label="ticking a box accomplishing a new goal permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Ticking a box, accomplishing a new goal</h3> <p>If you have a todo list and have some goals for yourself, why not put writing a book in there too. Think about how accomplished you feel when you actually do it. You will tackle a new challenge and prove to yourself that nothing is impossible if you put your energy into it.</p> <h3 id="you-will-learn-a-lot" style="position:relative;"><a href="#you-will-learn-a-lot" aria-label="you will learn a lot permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>You will learn a lot</h3> <p>Writing a book can help you learn a lot in a specific subject. When you are writing a book, it becomes important that you validate your knowledge and back everything with facts and data. During this process you will learn new things in every step of the way and that to me is worth all the time and energy you put into it.</p> <h3 id="you-might-make-money" style="position:relative;"><a href="#you-might-make-money" aria-label="you might make money permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>You might make money</h3> <p>Not every goal is about money, but if you can make a few extra bucks publishing your book, why not. You have put your time on it and getting rewards in form of a currency (😎) can be very helpful to anybody.</p> <p>Now that I mentioned my reasons, think about what else and let me know, I am keen to hear yours too. Enough about reasons, let’s talk about the process.</p> <h2 id="the-uhowu" style="position:relative;"><a href="#the-uhowu" aria-label="the uhowu permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The <u>HOW</u></h2> <p>It is no secret that writing a book requires discipline and good time management. But let me tell you it needs a little bit more than that, in my case anyway. You might have to put extra effort or be a bit more proficient than me, so take this with a grain of salt.</p> <h3 id="finding-a-topic" style="position:relative;"><a href="#finding-a-topic" aria-label="finding a topic permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Finding a topic</h3> <p>First thing you need to do is to find a topic, but you can’t find any topic. You need to find something that you’re good at it, and you really believe it’s important to share it with others. In my case <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview" target="_blank" rel="nofollow noopener noreferrer">Azure Bicep</a> is a new tool, there are not many books written on it, and I am really passionate about how it can help people achieve more by doing less.</p> <blockquote> <p>If you want to publish your book, you also need to find a topic where there are not many books available by other writers on it.</p> </blockquote> <h3 id="form-an-outline" style="position:relative;"><a href="#form-an-outline" aria-label="form an outline permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Form an outline</h3> <p>It’s important to have an outline to be able to tell a story. Yes, you heard me right, even a technical book needs a story if you want it to be effective. You need a <em>beginning</em>, <em>middle</em> and <em>end</em> which takes your readers on a journey. Start with simples, then move to advanced areas and then finish off by some best practices or something that compliments your points and helps them a few extra steps.</p> <h3 id="read-it-yourself" style="position:relative;"><a href="#read-it-yourself" aria-label="read it yourself permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Read it yourself</h3> <p>I can’t emphasis enough how important it is to read what you write first. If you feel you’re biased, get somebody else to read it and give you feedback. Sometimes we don’t realise how we fall into trap of making assumptions and the book turns out to be useless because of that.</p> <h3 id="set-a-schedule" style="position:relative;"><a href="#set-a-schedule" aria-label="set a schedule permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Set a schedule</h3> <p>If you are a full time employee like me, you need to set a schedule for yourself and make it a habit. Lock it in your calendar and make sure it’s a time with minimal disruptions. I used one hour every night from 10pm to 11pm when my kids were in bed and I had already finished my dad duties. But I kept it like that for 4 months until I finished the book and boy that helps.</p> <p>If that doesn’t work for you, you can set a goal for how many words need to write each day, week, months, etc.</p> <h3 id="use-writing-tools" style="position:relative;"><a href="#use-writing-tools" aria-label="use writing tools permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Use writing tools</h3> <p>Books are different that blog posts in a sense that they need to be credible. Having grammar errors or spelling mistakes will look really bad, so use a software which helps you proofread your writing.</p> <p>And that’s all I did to be able to be here writing this post.</p> <h2 id="finishing-touches" style="position:relative;"><a href="#finishing-touches" aria-label="finishing touches permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Finishing touches</h2> <p>It can become a really hard job even from start, so keep yourself motivated. If you face any setbacks or challenges, try to positively face them and work your way around solving those slowly and steadily.</p> <p>Reward yourself, give yourself a little treat at the end of every chapter, or section. This definitely helped me, so it will probably help you too.</p> <p>I hope this has motivated you to at list think about the possibility of wiring a book. Anyway, I didn’t talk about the book itself, so read on.</p> <h2 id="about-my-book" style="position:relative;"><a href="#about-my-book" aria-label="about my book permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>About my book</h2> <p><a href="https://www.amazon.com/gp/product/B09MFY582M/ref=dbs_a_def_rwt_bibl_vppi_i0" target="_blank" rel="nofollow noopener noreferrer">Infrastructure as code with Azure Bicep</a> is a book which takes you on a journey from what Azure Bicep is, to how it works and its syntax. Then you learn about all the bits and pieces to be able to write maintainable, reusable, modular templates to be able to deploy your resources into your Azure environment with confidence. It also covers the authoring experience and how amazing it is to work with it from Visual Studio Code and its extension.</p> <p>Then it covers how to use it in your CI/CD pipelines both on Azure DevOps and GitHub actions. And at last we will cover some advanced topics to add that extra bit of tidbits which makes you stand out when writing infrastructure as code such as some best practices and patterns when deploying multiple environments.</p> <p>So keep an eye out and support me and my book if you can 😍🙏🏽.</p><![CDATA[Crowbits - STEM toy for kids 😍]]>https://yashints.dev/blog/2021/07/19/crowbitshttps://yashints.dev/blog/2021/07/19/crowbitsMon, 19 Jul 2021 00:00:00 GMT<p>Last year I backed up the <a href="https://www.kickstarter.com/projects/elecrow/crowbits-electronic-blocks-for-stem-education-at-any-level" target="_blank" rel="nofollow noopener noreferrer">Crowbits</a> project on Kickstarter because I saw a potential for kids to learn so much by assembling these kits especially that it could be integrated with lego’s.</p> <p>At the moment you can purchase the kits from the <a href="https://www.elecrow.com/crowbits-kit/kits.html" target="_blank" rel="nofollow noopener noreferrer">Elecrow</a>’s website. And just worth mentioning that this post is not sponsored, nor do I have any association with the team or any money from sharing these links is in play.</p> <!--more--> <h2 id="what-are-they" style="position:relative;"><a href="#what-are-they" aria-label="what are they permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What are they?</h2> <p>To me Crowbits are the coolest coding toys that I’ve seen so far. With more than 80+ electronic blocks, and a kid friendly graphical software, it really stands up from other similar products and creates a whole other level of fun for kids (and adults, I had fun too 😁).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1000px; " > <a class="gatsby-resp-image-link" href="/static/c2058eaa57fae3fdcffd5e6541fdd8a6/a2510/crowbits1.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGQABAAIDAAAAAAAAAAAAAAAAAAIDAQQF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHrSrizuomq8iWhf//EABkQAAIDAQAAAAAAAAAAAAAAAAECEBESA//aAAgBAQABBQJmIOjDUCaWG5qxysf/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAaEAACAwEBAAAAAAAAAAAAAAABEQAQMQIh/9oACAEBAAY/AoFy69gZ2mZlf//EABsQAAIDAQEBAAAAAAAAAAAAAAERACExEEFx/9oACAEBAAE/IUkBwjNAdRzhEtezcrDxwOIBf2ZMCq5//9oADAMBAAIAAwAAABDox0D/xAAYEQACAwAAAAAAAAAAAAAAAAABEBEhMf/aAAgBAwEBPxAG9UBf/8QAFhEAAwAAAAAAAAAAAAAAAAAAASAx/9oACAECAQE/EDE//8QAHRABAAMBAAIDAAAAAAAAAAAAAQARITEQUWFxgf/aAAgBAQABPxC/M/DxhKnYC/w98DxIUX0uIGh0mW59EAuvcerV1ERbSAC60chP/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Crowbits kit" title="" src="/static/c2058eaa57fae3fdcffd5e6541fdd8a6/a2510/crowbits1.jpg" srcset="/static/c2058eaa57fae3fdcffd5e6541fdd8a6/6f81f/crowbits1.jpg 270w, /static/c2058eaa57fae3fdcffd5e6541fdd8a6/09d21/crowbits1.jpg 540w, /static/c2058eaa57fae3fdcffd5e6541fdd8a6/a2510/crowbits1.jpg 1000w" sizes="(max-width: 1000px) 100vw, 1000px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="modes" style="position:relative;"><a href="#modes" aria-label="modes permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Modes</h2> <p>There are two modes you could play with this set, no-code and code modes. In this mode you just stick the pieces together and make a project using cardboard, lego pieces, strings and other crafting tools.</p> <p><img src="/a0aa0c20efeed02ff93eb41191118b5a/robot.gif" alt="Crowbits robot walking"></p> <p>In the coding mode, they use a software which helps them understand how the order of pieces should be and what happens when they connect them together. They don’t need to write any code, rather drag and drop the blocks in the software to make something like a robot, or tell the robot what to do next.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/3b500477371f4b8c8871580b90c2ab12/29114/code.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.29629629629629%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB6ElEQVR42pWRz2/SYBjH+aM8mIwxlUxiogeNiUdvXjQxMWzGzOES4+LExIOXZcvE6eZl/HArTWG2/FrIaCgyWDekhREtqMwFKevbUtq+ry2KotGDT76H53v4PN/nyWNDfy84JMMUtPRn2YbN/LNXN+/MTngeu6e9tz1et8d7667VTM08mZycDmOYNXVoiO1nigykFTzrz7RwpvVmV0zsH5NFkciLi3TrKSMvpivR2OY/kxUZZLLsFq9rpvmwCisL/fVVpqtjCsIlLcGWkKF/T/oFH0rqy532cqqBJbK+dBeoepquZXKceaqsqLnD7XA94K/6NrPhPvf72nRdGn8tXFwora5vL21p3FH3XUx4GxE0qHUVtdFmhE5k73MomYuY4BGA9Q5StAGcew8u4MeXlxrB6M76HvoIkMaTMhe3UqDxqQM7AALQo/PsbtOIVhFVQ1/AAC7UlKs++fpcc24+tkxWCmyZKxe5MlvmD0zRxSrNHsQZ/jnBrLDoRQGt7SOpN4DzJXDN05xwf7105YZz3Olyuc6eO+84dWbEPmofdYw4xsZOO0+ctN+7/8D8uKLBnvHjbgsW6tLDWf7RDB+jUokkSVJUPJ7EwhvB4Fog4A+FggSBRwg8laREsd2/BQ6/yjQ6Qhr6z/oGn7gwhz+FkvEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Crowbits letscode software" title="" src="/static/3b500477371f4b8c8871580b90c2ab12/302a4/code.png" srcset="/static/3b500477371f4b8c8871580b90c2ab12/01bf6/code.png 270w, /static/3b500477371f4b8c8871580b90c2ab12/07484/code.png 540w, /static/3b500477371f4b8c8871580b90c2ab12/302a4/code.png 1080w, /static/3b500477371f4b8c8871580b90c2ab12/0d292/code.png 1620w, /static/3b500477371f4b8c8871580b90c2ab12/29114/code.png 1920w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="kits" style="position:relative;"><a href="#kits" aria-label="kits permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Kits</h2> <p>There are different types of kits you can purchase:</p> <ul> <li><strong>Hello kit</strong>: With this kit you will get a flashing window light, pandora’s box, cute little dog and morse code machine.</li> <li><strong>Explorer kit</strong>: With this kit you will jump a notch to minsweeper, climbing monkey, piggy bank, the smart fan, ski and quadruped robots, and much more.</li> <li><strong>Inventor kit</strong>: This kit gets you the gesture control and bluetooth cars, obstacle avoidance car, elevator, automatic door, and a whole bunch of cool other projects.</li> <li><strong>Creator kit</strong>: You can build a horse racing project, catch the fruit game, crazy bird, tank wars and much more with this one.</li> <li><strong>Master kit</strong>: This one gets you phone, game console, radar and more advanced projects.</li> </ul> <h2 id="so" style="position:relative;"><a href="#so" aria-label="so permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>So</h2> <p>So don’t wait, go order your desired kit and get your kids (or yourself) busy building cool stuff.</p><![CDATA[Azure Active Directory - Concepts simplified 🔑]]>https://yashints.dev/blog/2021/07/02/azure-ad-conceptshttps://yashints.dev/blog/2021/07/02/azure-ad-conceptsFri, 02 Jul 2021 00:00:00 GMT<p><a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals#application-registration" target="_blank" rel="nofollow noopener noreferrer">Application registration, service principal</a>, <a href="https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview" target="_blank" rel="nofollow noopener noreferrer">system-assigned managed identity, user-assigned managed identity</a>, Enterprise Application, these are just a few concepts in Microsoft Identity Platform which helps businesses protect their applications and provide authentication and authorization using Azure Active Directory (aka AAD).</p> <p>There are many scenarios which can be covered using these concepts and although Microsoft has a ton of documentation around these, people get confused simply because of sheer amount of information to digest. So the point of these series is to get people to understand these concepts and apply them in their products developed on top of Azure AD without having to get information overload. In this post we will cover the basics.</p> <!--more--> <h2 id="pre-requisites" style="position:relative;"><a href="#pre-requisites" aria-label="pre requisites permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Pre-requisites</h2> <p>Before we start going through these concepts, it’s worth mentioning we’re assuming our readers already know about a few concepts such as:</p> <ul> <li><a href="https://docs.microsoft.com/en-us/azure/active-directory/authentication/overview-authentication" target="_blank" rel="nofollow noopener noreferrer">Authentication</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-vs-authorization" target="_blank" rel="nofollow noopener noreferrer">Authorization</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/active-directory/authentication/concept-mfa-howitworks" target="_blank" rel="nofollow noopener noreferrer">Multi-Factor authentication</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/what-is-single-sign-on" target="_blank" rel="nofollow noopener noreferrer">Single sign-on</a></li> </ul> <p>Before we go into Azure AD topics, we need to be on the same page with a few standards which are widely used in our industry.</p> <h2 id="openid-connect" style="position:relative;"><a href="#openid-connect" aria-label="openid connect permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>OpenID Connect</h2> <p><a href="https://openid.net/connect/" target="_blank" rel="nofollow noopener noreferrer">OpenID Connect (aka OIDC)</a> is an authentication protocol which makes things simple when it comes to authenticating a user or device. It leverages a trusted identity provider for authentication, and deals with applications in which users will sign into. For more information please refer to <a href="https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/auth-oidc" target="_blank" rel="nofollow noopener noreferrer">our documentation here</a>.</p> <h2 id="oauth-20" style="position:relative;"><a href="#oauth-20" aria-label="oauth 20 permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>OAuth 2.0</h2> <p>While OIDC deals with authentication, it relies on <a href="https://oauth.net/2/" target="_blank" rel="nofollow noopener noreferrer">OAuth 2.0</a> for authorization which allows a user to grant limited access to an application to its protected resources. This protocol is designed to work with HTTP protocol and separates the role of client from the resource owner. Again for more information please refer to <a href="https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/auth-oauth2" target="_blank" rel="nofollow noopener noreferrer">our documentation</a>.</p> <p>Now that we know enough about these concepts, let’s dip our toes into the ocean.</p> <h2 id="service-principal" style="position:relative;"><a href="#service-principal" aria-label="service principal permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Service Principal</h2> <p>Microsoft identity platform allows accessing resources to entities by a security principal which in case of a user is called <em>User Principal</em> and in terms of an application is called <em>Security Principal</em>. This object defines the access policy and permissions for the application in your tenant. This object is responsible for enabling core features such as authentication and authorization. There are different types of service principal, application, managed identity and legacy.</p> <h2 id="application-registration" style="position:relative;"><a href="#application-registration" aria-label="application registration permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Application Registration</h2> <p>First off, we need to emphasize something, don’t get this confused with a web or console application. Since OAuth 2.0 and OpenID Connect work allow your applications to deal with users, Microsoft Identity Platform is restricting the identity and access management only for registered applications. This is where application registration comes into place, think of it as an entity representing your own application. This entity is registered in your tenant (Azure Active Directory instance dedicated to your organization) and you have full control over it.</p> <p>Through this app registration you get to specify which type of application will be using this to perform authentication, what type of scenarios are going to be covered (Client Credentials flow, Authorization Code flow, etc), whether it’s a single tenant app or a multi tenant one, etc. All of these are called identity configuration.</p> <p>Your app registration has two entities associated to it behind the scenes, <em>Application Object</em> and a <em>Service Principal Object</em>.</p> <h3 id="application-object" style="position:relative;"><a href="#application-object" aria-label="application object permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Application Object</h3> <p>This object hold all the configuration of your application such as its name, and it’s properties such as a callback URL, logout URL, etc.</p> <h2 id="managed-identity" style="position:relative;"><a href="#managed-identity" aria-label="managed identity permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Managed Identity</h2> <p>Managed identities are created to allow organizations and specifically developers to enable authentication and authorization without the need to have any credentials in place. This will simplify and secure the application implementation and mitigates many security risks which is caused by credentials leaked either by being committed to source code or being shared with many people. Just bare in mind that managed identities are designed to grant access to Azure Resources, meaning you can’t use a managed identity to secure an application hosted outside Azure.</p> <p>There are two types of managed identities, system assigned and user assigned. System assigned are created and destroyed with the service they belong to, whereas user assigned are independent services which have their own lifecycle and can be assigned to multiple cloud resources. You can see all of these in the below picture:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/f1a459b5982d9051293a1efcebab7814/eb645/AADIdentities.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 66.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABJ0AAASdAHeZh94AAACC0lEQVR42qWTS2/TQBRG83/5B6yQUBdly4IFogghUqnLIsRTiFJoqQJUaUrTRkmTOE7sJI7jd/zM4Y7bsOlDCK5kj+3xnPvd785UuIyCJXZHY7Z/xKQ3ICty/iUq6rZcKiBoOzW69zfQ9w5J/geoIglDAt/DNca4c4e+YRBEEXEQEMq4VFn/BugKqDEe82U45MxxKNIUQ8b1owYPz8+pTqZ8ME2yOOaimtvBlZ8CWmueclcAm90esSTwPI/v8zkv9CFVXadpWXjOnCRNLqDFzdCKLQs116UvoxcGxIsFM9siiQIW8q4MXsg305hw2mij9w1cz7lRaaUoinJRmsR4doDWNmk3dHrHU/TOVFTFROGCk4MBX18d8+NjC8cKiKLweuAqk+v4tGtTWt8mdGoWxmmAKwlCpVr867VGnNS6NA7adJoaY/E9Fb+vdvlS+SKKsQy3hPieAomXvluW6/s+eZ6hqimWRfms5jVNE2tiqS69um2U0ixPybK0XLBSPrNm2HKV1khkMqe21ZuzPZ79es3nUR1bGrb6v3KdD2mc0Bp2eVrb5tHuFpv1t/SH2sWJEnAgNjypb3Pn5ToP9p8zsE1WlV4LLLKcznRAtfmetd0N7n16zPG4TZ5k5bzaVpptsHX4js6oTygWFbcpVBGWPkZ0TY3hzMSVU7RqgmpSFEiX8yW5JM+y7I+HvwGcZeEyxjtYUQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Azure Active Directory Security Principal types" title="" src="/static/f1a459b5982d9051293a1efcebab7814/302a4/AADIdentities.png" srcset="/static/f1a459b5982d9051293a1efcebab7814/01bf6/AADIdentities.png 270w, /static/f1a459b5982d9051293a1efcebab7814/07484/AADIdentities.png 540w, /static/f1a459b5982d9051293a1efcebab7814/302a4/AADIdentities.png 1080w, /static/f1a459b5982d9051293a1efcebab7814/0d292/AADIdentities.png 1620w, /static/f1a459b5982d9051293a1efcebab7814/b3608/AADIdentities.png 2160w, /static/f1a459b5982d9051293a1efcebab7814/eb645/AADIdentities.png 2500w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="enterprise-application" style="position:relative;"><a href="#enterprise-application" aria-label="enterprise application permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Enterprise Application</h2> <p>And at last we have to talk about Enterprise Applications, these applications are generally representing applications that are created by other organizations and are accessible for you to subscribe to or use. Some are free to use whereas some you have to pay for. Something worth mentioning is that you would have an enterprise application even for your own apps. You can view a list of these applications from the <a href="https://docs.microsoft.com/en-us/azure/active-directory/saas-apps/" target="_blank" rel="nofollow noopener noreferrer">Azure App Gallery</a>. Once you have added the application to your tenant it will appear in your enterprise application menu in your Azure AD blade.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <a class="gatsby-resp-image-link" href="/static/d34bdb1d13f055f3a40484db210fe3ba/5a190/enterprise-applications-in-nav.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 60.370370370370374%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABJ0AAASdAHeZh94AAACBUlEQVR42o1SXavUMBDtv1Yf7oe7y15WEH3a+wU+CfsbBF2vD1fh+iAiKCirqNvWbZsmbfqRNk2Ok+xFFxSvA4dJhsmZMzMJThdP8PbjGu8+hfiwivBwvsD+ZI7D6QlGszOMZ+ce7jy5d47J9BQH4zn2xse4NTrB/uwR9ih2e3SMOxQPDp5z3H2a4nDJ8PiNwLOvGsvvFstvBhdrixchPC4IlzGweN/j6LLG9GWNMfmjVw3uX7V48Frh7IojWGUtwjhBynKIQkJ1Gnowf0Jv0TsMu36AbAeIpqezRhDzGlGaI+USvKx9UKnOE3fk9TCg6zpCT4QafU/oFHbNWuthjEGQ8xyMMbRti7qSSEWLXGoiV2AFnZ0vtz6nOyt7UmNQE6dUFnUHDxIITh0GhZSerO97KPJJkiHLsl/Vja++o8Yr2p6lrHw3xlgPIQoEWdVCE5kzpRREWVKiRF1XVKjB4FtWpL6iWO1zXGvOtO5pLMq/NzQOIYRTWIClKSoiGmhWiSgRRhGiKKZFMa98s9nQPUJKeaXLoyJ/s4qKBuvVF/z4vEJMEGGIhoKSHrkNmutEWVVe2U3mCgVJkoOTAkUkmlpyCpIkoXnWYLJD3gwYqLW2/d3qPwnLpkFRFv5LuNW7BfE8386Lvog2Fv9rnpDRIHnO0RBx68lLT+bvRG6vV2rtzcRO1E+OA4gdHm2BBwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Enterprise Applications in Azure Active Directory" title="" src="/static/d34bdb1d13f055f3a40484db210fe3ba/5a190/enterprise-applications-in-nav.png" srcset="/static/d34bdb1d13f055f3a40484db210fe3ba/01bf6/enterprise-applications-in-nav.png 270w, /static/d34bdb1d13f055f3a40484db210fe3ba/07484/enterprise-applications-in-nav.png 540w, /static/d34bdb1d13f055f3a40484db210fe3ba/5a190/enterprise-applications-in-nav.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>There are multiple different concepts when it comes to Azure Active Directory and using it for authentication and authorization of your applications. In this post we reviewed a few of the terms which are used in many different scenarios and saw what they are and when the come into picture. Hopefully now you have the background to delve into our next series of posts which cover scenarios and how to implement them using these entities.</p> <h2 id="up-next" style="position:relative;"><a href="#up-next" aria-label="up next permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Up next</h2> <p>There are many different scenarios which can be addressed using Azure AD. Here are a few:</p> <ul> <li>A single page application (aka SPA) consuming a REST API both hosted on <a href="https://azure.microsoft.com/en-au/services/app-service/" target="_blank" rel="nofollow noopener noreferrer">Azure App Services</a> (using two separate app registration)</li> <li>A SPA consuming an <a href="https://azure.microsoft.com/en-au/services/functions/" target="_blank" rel="nofollow noopener noreferrer">Azure Function</a> (using app registration and managed identity)</li> <li>A SPA consuming an API hosted on <a href="https://azure.microsoft.com/en-au/services/api-management/" target="_blank" rel="nofollow noopener noreferrer">Azure API Management</a></li> <li>A multi-tenant API with a allowed list of tenants (using app registration and managed identity)</li> <li>Device code flow for input constraint devices such as Smart TV</li> <li>Using managed identity to access protected resources with EasyAuth</li> </ul> <p>In the upcoming series we will tackle each of these in turn, so stay tuned.</p><![CDATA[From ARM to Bicep 💪🏽]]>https://yashints.dev/blog/2021/05/10/from-arm-to-bicephttps://yashints.dev/blog/2021/05/10/from-arm-to-bicepMon, 10 May 2021 00:00:00 GMT<p>If you have deployed a resource in <a href="https://azure.microsoft.com/" target="_blank" rel="nofollow noopener noreferrer">Microsoft Azure</a> as part of your CI/CD pipeline you have probably worked with <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview" target="_blank" rel="nofollow noopener noreferrer">ARM templates</a>. These templates can be used to automate your resource deployment to Azure and help you to have consistent environments whether it’s for testing, development or production purposes. However, there are some shortcomings when it comes to complex environments especially when you have many resources and the dependency between them makes the templates to be either super busy, very complex, or unreadable.</p> <p>For that Microsoft has introduced <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/bicep-overview" target="_blank" rel="nofollow noopener noreferrer">Bicep</a> which is designed to overcome these issues and help you with your infrastructure as code setup.</p> <!--more--> <h2 id="whats-not-working-with-arm-templates" style="position:relative;"><a href="#whats-not-working-with-arm-templates" aria-label="whats not working with arm templates permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What’s not working with ARM templates?</h2> <p>Although there are some great features which make working with ARM templates a good experience such as functions, variables, nested templates etc, there is some room for improvements regarding below which has been raised by the community:</p> <ul> <li><strong>No comments</strong>: As of now you can’t use comments in the JSON files used by <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview" target="_blank" rel="nofollow noopener noreferrer">Azure Resource Manager</a>. Since the users of ARM templates are mainly developers, comments would potentially help the next user to better understand the template and what’s going on in it. I’d personally argue with the fact that if you need comments in a template it probably means you need to refactor it, however, this is something which could come handy at times.</li> <li><strong>Parameter duplication</strong>: Since ARM templates are reusable, you would normally use parameters for customising the resource naming, number of resources to be deployed, pricing tier and so on. The problem is that these parameters would be needed and if you haven’t provided a default value you would get an error. So you might end up with lots of parameters repeated in different files for different environments, or simply replicating a single file and have lot’s of extra parameters which are not needed.</li> <li><strong>Validation</strong>: Although there is a validate command you can use to validate your templates, there might be times where validation doesn’t show you enough information or is not enough to prevent a failure in the actual run.</li> </ul> <h2 id="what-is-bicep" style="position:relative;"><a href="#what-is-bicep" aria-label="what is bicep permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What is Bicep?</h2> <p>Bicep is a DSL (domain specific language) which can be used to write your Infrastructure as Code (IaC). Instead of writing ARM templates you write your code with Bicep and it will transpile it to ARM for you. It simplifies the authoring experience and addresses some of the issues we mentioned earlier. Compared to using JSON, Bicep can help you simplify the template definition a great deal.</p> <p>Let’s see this using a simple example. Imagine you are trying to create a storage account in Azure, with ARM template this is the minimum you will need:</p> <div class="gatsby-code-button-container" data-toaster-id="19581576941696864000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`{ &quot;\$schema&quot;: &quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;, &quot;contentVersion&quot;: &quot;1.0.0.0&quot;, &quot;parameters&quot;: { &quot;storageAccountType&quot;: { &quot;type&quot;: &quot;secureString&quot; } }, &quot;variables&quot;: { &quot;diagStorageAccountName&quot;: &quot;[concat('diags', uniqueString(resourceGroup().id))]&quot; }, &quot;resources&quot;: [ { &quot;type&quot;: &quot;Microsoft.Storage/storageAccounts&quot;, &quot;apiVersion&quot;: &quot;2019-06-01&quot;, &quot;name&quot;: &quot;[variables('diagStorageAccountName')]&quot;, &quot;location&quot;: &quot;[resourceGroup().location]&quot;, &quot;sku&quot;: { &quot;name&quot;: &quot;[parameters('storageAccountType')]&quot; }, &quot;kind&quot;: &quot;Storage&quot; } ] }`, `19581576941696864000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"$schema"</span><span class="token operator">:</span> <span class="token string">"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"</span><span class="token punctuation">,</span> <span class="token property">"contentVersion"</span><span class="token operator">:</span> <span class="token string">"1.0.0.0"</span><span class="token punctuation">,</span> <span class="token property">"parameters"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"storageAccountType"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"secureString"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"variables"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"diagStorageAccountName"</span><span class="token operator">:</span> <span class="token string">"[concat('diags', uniqueString(resourceGroup().id))]"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"resources"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"Microsoft.Storage/storageAccounts"</span><span class="token punctuation">,</span> <span class="token property">"apiVersion"</span><span class="token operator">:</span> <span class="token string">"2019-06-01"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"[variables('diagStorageAccountName')]"</span><span class="token punctuation">,</span> <span class="token property">"location"</span><span class="token operator">:</span> <span class="token string">"[resourceGroup().location]"</span><span class="token punctuation">,</span> <span class="token property">"sku"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"[parameters('storageAccountType')]"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"Storage"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Using Bicep it will be simplified to:</p> <div class="gatsby-code-button-container" data-toaster-id="83378654092479790000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@secure() param storageAccountType string param location string = resourceGroup().location var diagStorageAccountName = concat('diags', uniqueString(resourceGroup().id)) resource diagsAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: diagStorageAccountName location: location sku: { name: storageAccountType } kind: 'Storage' }`, `83378654092479790000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">@<span class="token function">secure</span><span class="token punctuation">(</span><span class="token punctuation">)</span> param storageAccountType string param location string <span class="token operator">=</span> <span class="token function">resourceGroup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>location <span class="token keyword">var</span> diagStorageAccountName <span class="token operator">=</span> <span class="token function">concat</span><span class="token punctuation">(</span><span class="token string">'diags'</span><span class="token punctuation">,</span> <span class="token function">uniqueString</span><span class="token punctuation">(</span><span class="token function">resourceGroup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> resource diagsAccount <span class="token string">'Microsoft.Storage/storageAccounts@2019-06-01'</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> diagStorageAccountName <span class="token literal-property property">location</span><span class="token operator">:</span> location <span class="token literal-property property">sku</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> storageAccountType <span class="token punctuation">}</span> <span class="token literal-property property">kind</span><span class="token operator">:</span> <span class="token string">'Storage'</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>You can see how much time and space you could be saving if you were to use Bicep.</p> <h2 id="benefits" style="position:relative;"><a href="#benefits" aria-label="benefits permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Benefits</h2> <p>When it comes to the benefits of using Bicep, there is a list published in our documentation:</p> <ul> <li>Support for all resource types and API versions.</li> <li>Better authoring experience using editors such as VS Code (you will get validation, type-safety, intellisense).</li> <li>Modularity can be achieved using <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/bicep-modules" target="_blank" rel="nofollow noopener noreferrer">modules</a>. You can have modules representing an entire environment or a set of shared resources and use them anywhere in a Bicep file.</li> <li>Integration with Azure services such as Azure Policy, Templates specs, and Blueprints.</li> <li>No need to store a state file or keep any state. You can even use the <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-deploy-what-if" target="_blank" rel="nofollow noopener noreferrer">what-if operation</a> to preview your changes before deploying them.</li> <li>Bicep is open source with a strong community supporting it.</li> </ul> <h2 id="syntax" style="position:relative;"><a href="#syntax" aria-label="syntax permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Syntax</h2> <p>Every Bicep resource will have the below syntax:</p> <div class="gatsby-code-button-container" data-toaster-id="53294306213039145000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`resource <symbolic-name> '<resource-type>@<api-version>\` = { //properties name: 'bicepstorage2063' location: 'northcentralus' properties: { //...sub properties } }`, `53294306213039145000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">resource <span class="token operator">&lt;</span>symbolic<span class="token operator">-</span>name<span class="token operator">></span> '<span class="token operator">&lt;</span>resource<span class="token operator">-</span>type<span class="token operator">></span>@<span class="token operator">&lt;</span>api<span class="token operator">-</span>version<span class="token operator">></span>` <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">//properties</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'bicepstorage2063'</span> <span class="token literal-property property">location</span><span class="token operator">:</span> <span class="token string">'northcentralus'</span> <span class="token literal-property property">properties</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token comment">//...sub properties</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Where:</p> <ul> <li><code class="language-text">resource</code>: is a reserved keyword.</li> <li><code class="language-text">symbolic name</code>: is an identifier within the Bicep file which can be used to reference this resource elsewhere.</li> <li><code class="language-text">resource-type</code>: is the type of the resource you’re defining, e.g. <code class="language-text">Microsoft.Storage</code>.</li> <li><code class="language-text">api-version</code>: each resource provider publishes its own API version which defines which version of the Azure Resource Manager REST API should be used to deploy this resource.</li> <li><code class="language-text">properties</code>: these are the resource specific properties. For example every resource has a <code class="language-text">name</code> and <code class="language-text">location</code>. In addition some have sub properties which you can pass on.</li> </ul> <h2 id="parameters" style="position:relative;"><a href="#parameters" aria-label="parameters permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Parameters</h2> <p>When we talk about infrastructure as a code and reusability of our templates, we definitely end up using parameters to customise our resources. Be its name, sku, username or password, we will need to change these per environment or application.</p> <p>In a Bicep file you can define the parameters that need to be passed to it when deploying resources. You can put validation on the parameter value, provide default value, and limit it to allowed values. The format of a parameter will be such as below:</p> <div class="gatsby-code-button-container" data-toaster-id="73658214581785805000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`param <parameter-name> <parameter-type> = <parameter-value>`, `73658214581785805000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">param <span class="token operator">&lt;</span>parameter<span class="token operator">-</span>name<span class="token operator">></span> <span class="token operator">&lt;</span>parameter<span class="token operator">-</span>type<span class="token operator">></span> <span class="token operator">=</span> <span class="token operator">&lt;</span>parameter<span class="token operator">-</span>value<span class="token operator">></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Where:</p> <ul> <li><code class="language-text">param</code>: is a reserved keyword.</li> <li><code class="language-text">parameter-name</code> is the name of the parameter.</li> <li><code class="language-text">parameter-type</code>: is the type of the parameter such as <code class="language-text">string</code>, <code class="language-text">object</code>, etc.</li> <li><code class="language-text">parameter-value</code>: is the value of the parameter you’re passing in.</li> </ul> <p>Let’s review two examples to get a better understanding of the structure.</p> <div class="gatsby-code-button-container" data-toaster-id="62944884126366940000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@minLength(3) @maxLength(24) param storageName string`, `62944884126366940000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">@<span class="token function">minLength</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> @<span class="token function">maxLength</span><span class="token punctuation">(</span><span class="token number">24</span><span class="token punctuation">)</span> param storageName string</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>In this example you’re limiting the <code class="language-text">storageName</code> parameter’s value length to be between 3 and 24 characters. Or:</p> <div class="gatsby-code-button-container" data-toaster-id="33567622693547983000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@allowed([ 'Standard_LRS' 'Standard_GRS' 'Standard_RAGRS' 'Standard_ZRS' 'Premium_LRS' 'Premium_ZRS' 'Standard_GZRS' 'Standard_RAGZRS' ]) param storageRedundancy string = 'Standard_LRS'`, `33567622693547983000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">@<span class="token function">allowed</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token string">'Standard_LRS'</span> <span class="token string">'Standard_GRS'</span> <span class="token string">'Standard_RAGRS'</span> <span class="token string">'Standard_ZRS'</span> <span class="token string">'Premium_LRS'</span> <span class="token string">'Premium_ZRS'</span> <span class="token string">'Standard_GZRS'</span> <span class="token string">'Standard_RAGZRS'</span> <span class="token punctuation">]</span><span class="token punctuation">)</span> param storageRedundancy string <span class="token operator">=</span> <span class="token string">'Standard_LRS'</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>In this example you’re specifying the allowed values for the <code class="language-text">storageRedundancy</code> parameter and also provide the default value if nothing is provided during the deployment.</p> <p>With ARM templates you had to use a separate file to pass the parameters during the deployments usually with a name ending in <code class="language-text">.parameters.json</code>. In Bicep you need to use the same JSON file to pass the parameters in:</p> <div class="gatsby-code-button-container" data-toaster-id="43787735925740450000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`{ &quot;\$schema&quot;: &quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#&quot;, &quot;contentVersion&quot;: &quot;1.0.0.0&quot;, &quot;parameters&quot;: { &quot;storageName&quot;: { &quot;value&quot;: &quot;myuniquestoragename&quot; }, &quot;storageRedundancy&quot;: { &quot;value&quot;: &quot;Standard_GZRS&quot; } } }`, `43787735925740450000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"$schema"</span><span class="token operator">:</span> <span class="token string">"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"</span><span class="token punctuation">,</span> <span class="token property">"contentVersion"</span><span class="token operator">:</span> <span class="token string">"1.0.0.0"</span><span class="token punctuation">,</span> <span class="token property">"parameters"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"storageName"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"myuniquestoragename"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"storageRedundancy"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"Standard_GZRS"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="variables" style="position:relative;"><a href="#variables" aria-label="variables permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Variables</h2> <p>Similar to parameters, variables play an important part in our templates, especially when it comes to naming conventions. These can store complex expressions to keep our templates clean and their maintenance simple. In Bicep variables are defined using the <code class="language-text">var</code> keyword:</p> <div class="gatsby-code-button-container" data-toaster-id="78296541307306200000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var <variable-name> = <value>`, `78296541307306200000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">var</span> <span class="token operator">&lt;</span>variable<span class="token operator">-</span>name<span class="token operator">></span> <span class="token operator">=</span> <span class="token operator">&lt;</span>value<span class="token operator">></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Where <code class="language-text">variable-name</code> is the name of your variable. For example in our previous Bicep file we could have used a variable for our storage name:</p> <div class="gatsby-code-button-container" data-toaster-id="75869708988730700000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`var storageAccName = 'sa\${uniqueString(resourceGroup().id)}' resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: storageAccountName //... }`, `75869708988730700000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">var</span> storageAccName <span class="token operator">=</span> <span class="token string">'sa${uniqueString(resourceGroup().id)}'</span> resource stg <span class="token string">'Microsoft.Storage/storageAccounts@2019-06-01'</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> storageAccountName <span class="token comment">//...</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Since we need a unique name for our storage account the <code class="language-text">uniqueString</code> function is used (Don’t worry about that for now). The point is that we can create variables and use them in our template with ease.</p> <p>There are multiple variable types you can use:</p> <ul> <li>String</li> <li>Boolean</li> <li>Numeric</li> <li>Object</li> <li>Array</li> </ul> <h2 id="expressions" style="position:relative;"><a href="#expressions" aria-label="expressions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Expressions</h2> <p>Expressions are used in our templates for variety of reasons, from getting the current location of the resource group to subscription id or the current datetime.</p> <h3 id="functions" style="position:relative;"><a href="#functions" aria-label="functions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>functions</h3> <p>The good thing is that <strong>ANY</strong> valid <a href="https://docs.microsoft.com/azure/azure-resource-manager/templates/template-functions" target="_blank" rel="nofollow noopener noreferrer">ARM template function</a> is also a valid Bicep function.</p> <div class="gatsby-code-button-container" data-toaster-id="65331181020683985000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`param currentTime string = utcNow() var location = resourceGroup().location var makeCapital = toUpper('all lowercase')`, `65331181020683985000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">param currentTime string <span class="token operator">=</span> <span class="token function">utcNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">var</span> location <span class="token operator">=</span> <span class="token function">resourceGroup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>location <span class="token keyword">var</span> makeCapital <span class="token operator">=</span> <span class="token function">toUpper</span><span class="token punctuation">(</span><span class="token string">'all lowercase'</span><span class="token punctuation">)</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h3 id="ternary-operator" style="position:relative;"><a href="#ternary-operator" aria-label="ternary operator permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Ternary operator</h3> <p>To use conditions in your deployments you would use the <code class="language-text">if</code> function in ARM templates, however, that’s not supported in Bicep. Instead, you can leverage the <strong>ternary operator</strong>:</p> <div class="gatsby-code-button-container" data-toaster-id="78816266952296380000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`param globalRedundancy bool = true resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: storageAccountName location: location kind: 'Storage' sku: { name: globalRedundancy ? 'Standard_GRS' : 'Standard_LRS' // if true --> GRS, else --> LRS } }`, `78816266952296380000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">param globalRedundancy bool <span class="token operator">=</span> <span class="token boolean">true</span> resource stg <span class="token string">'Microsoft.Storage/storageAccounts@2019-06-01'</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> storageAccountName <span class="token literal-property property">location</span><span class="token operator">:</span> location <span class="token literal-property property">kind</span><span class="token operator">:</span> <span class="token string">'Storage'</span> <span class="token literal-property property">sku</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> globalRedundancy <span class="token operator">?</span> <span class="token string">'Standard_GRS'</span> <span class="token operator">:</span> <span class="token string">'Standard_LRS'</span> <span class="token comment">// if true --> GRS, else --> LRS</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="output" style="position:relative;"><a href="#output" aria-label="output permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Output</h2> <p>ARM templates have an output section where you could send information out of your pipeline to be accessed within other deployments or subsequent tasks. In Bicep you have the same concept via the <code class="language-text">output</code> keyword.</p> <div class="gatsby-code-button-container" data-toaster-id="64153537655130920000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = { //... } output storageId string = stg.id`, `64153537655130920000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">resource stg <span class="token string">'Microsoft.Storage/storageAccounts@2019-06-01'</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">//...</span> <span class="token punctuation">}</span> output storageId string <span class="token operator">=</span> stg<span class="token punctuation">.</span>id</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>This will return the storage id out to be used later.</p> <h2 id="loops" style="position:relative;"><a href="#loops" aria-label="loops permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Loops</h2> <p>In ARM templates if you wanted to deploy a resource multiple times you could leverage the <code class="language-text">copy</code> operator to add a resource <code class="language-text">n</code> times based on the loop count. In Bicep you have the <code class="language-text">for</code> operator at your disposal:</p> <div class="gatsby-code-button-container" data-toaster-id="95015330555116240000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`resource foo 'my.provider/type@2021-03-01' = [for <ITERATOR_NAME> in <ARRAY> = {...}]`, `95015330555116240000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">resource foo <span class="token string">'my.provider/type@2021-03-01'</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token keyword">for</span> <span class="token operator">&lt;</span><span class="token constant">ITERATOR_NAME</span><span class="token operator">></span> <span class="token keyword">in</span> <span class="token operator">&lt;</span><span class="token constant">ARRAY</span><span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">]</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Where <code class="language-text">ITERATOR_NAME</code> is a new symbol that’s only available inside your resource declaration.</p> <div class="gatsby-code-button-container" data-toaster-id="13482385385837836000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`param containerNames array = [ 'images' 'videos' 'pdf' ] resource blob 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = [for name in containerNames: { name: '\${stg.name}/default/\${name}' //... }]`, `13482385385837836000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">param containerNames array <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token string">'images'</span> <span class="token string">'videos'</span> <span class="token string">'pdf'</span> <span class="token punctuation">]</span> resource blob <span class="token string">'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01'</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token keyword">for</span> name <span class="token keyword">in</span> <span class="token literal-property property">containerNames</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'${stg.name}/default/${name}'</span> <span class="token comment">//...</span> <span class="token punctuation">}</span><span class="token punctuation">]</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>This snippet creates three containers within the storage account in a loop.</p> <h2 id="existing-keyword" style="position:relative;"><a href="#existing-keyword" aria-label="existing keyword permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Existing keyword</h2> <p>If you want to deploy a resource which is depending on an existing resource you can leverage the <code class="language-text">existing</code> keyword.</p> <div class="gatsby-code-button-container" data-toaster-id="12417934017992560000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' existing = { name: storageAccountName }`, `12417934017992560000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">resource stg <span class="token string">'Microsoft.Storage/storageAccounts@2019-06-01'</span> existing <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> storageAccountName <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>You won’t need the other properties since the resource already exists. You need enough information to be able to identify the resource. Now that you have this reference, you can use it in other parts of your deployment.</p> <h2 id="modules" style="position:relative;"><a href="#modules" aria-label="modules permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Modules</h2> <p>In ARM templates you had the concept of linked templates when it came to reuse a template in other deployments. In Bicep you have <code class="language-text">modules</code>. You can define a resource in a module and reuse that module in other Bicep files.</p> <div class="gatsby-code-button-container" data-toaster-id="51474139027401590000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`. ├── main.bicep └── stg.bicep`, `51474139027401590000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token builtin class-name">.</span> ├── main.bicep └── stg.bicep</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>In our <code class="language-text">stg</code> file you will define the resource, its parameters, variables, outputs, etc:</p> <div class="gatsby-code-button-container" data-toaster-id="45489524699301180000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`//stg.bicep param storageAccountName var storageSku = 'Standard_LRS' resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: storageAccountName location: resourceGroup().location kind: 'Storage' sku: { name: storageSku } }`, `45489524699301180000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token comment">//stg.bicep</span> param storageAccountName <span class="token keyword">var</span> storageSku <span class="token operator">=</span> <span class="token string">'Standard_LRS'</span> resource stg <span class="token string">'Microsoft.Storage/storageAccounts@2019-06-01'</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> storageAccountName <span class="token literal-property property">location</span><span class="token operator">:</span> <span class="token function">resourceGroup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>location <span class="token literal-property property">kind</span><span class="token operator">:</span> <span class="token string">'Storage'</span> <span class="token literal-property property">sku</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> storageSku <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>And in the <code class="language-text">main</code> file you will reuse the storage account as a module using the <code class="language-text">module</code> keyword:</p> <div class="gatsby-code-button-container" data-toaster-id="60132925876469080000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`//main.bicep module stg './storage.bicep' = { name: 'storageDeploy' params: { storageAccountName: '<YOURUNIQUESTORAGENAME>' } } output storageName array = stg.outputs.containerProps`, `60132925876469080000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token comment">//main.bicep</span> module stg <span class="token string">'./storage.bicep'</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'storageDeploy'</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">storageAccountName</span><span class="token operator">:</span> <span class="token string">'&lt;YOURUNIQUESTORAGENAME>'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> output storageName array <span class="token operator">=</span> stg<span class="token punctuation">.</span>outputs<span class="token punctuation">.</span>containerProps</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>You only need to pass the required properties which in case of our storage account is the name.</p> <h2 id="the-code-classlanguage-textanycode-keyword" style="position:relative;"><a href="#the-code-classlanguage-textanycode-keyword" aria-label="the code classlanguage textanycode keyword permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The <code class="language-text">any</code> keyword</h2> <p>There might be some cases where Bicep throws a false positive when it comes to errors or warnings. This might happen based on different situations such as the API not having the correct type definition. You can use the <code class="language-text">any</code> keyword to get around these situations when defining resources which have incorrect types assigned. One of examples is the container instances CPU and Memory properties which expect an <code class="language-text">int</code>, but in fact they are <code class="language-text">number</code> since you can pass non-integer values such as <code class="language-text">0.5</code>.</p> <div class="gatsby-code-button-container" data-toaster-id="15696117545730015000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`resource wpAci 'microsoft.containerInstance/containerGroups@2019-12-01' = { name: 'wordpress-containerinstance' location: location properties: { containers: [ { name: 'wordpress' properties: { ... resources: { requests: { cpu: any('0.5') memoryInGB: any('0.7') } } } } ] } }`, `15696117545730015000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js">resource wpAci <span class="token string">'microsoft.containerInstance/containerGroups@2019-12-01'</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'wordpress-containerinstance'</span> <span class="token literal-property property">location</span><span class="token operator">:</span> location <span class="token literal-property property">properties</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">containers</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'wordpress'</span> <span class="token literal-property property">properties</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token literal-property property">resources</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">requests</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">cpu</span><span class="token operator">:</span> <span class="token function">any</span><span class="token punctuation">(</span><span class="token string">'0.5'</span><span class="token punctuation">)</span> <span class="token literal-property property">memoryInGB</span><span class="token operator">:</span> <span class="token function">any</span><span class="token punctuation">(</span><span class="token string">'0.7'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>By using <code class="language-text">any</code> and passing the value you can get around the possible errors which might be raised during the build or the validation stage.</p> <h2 id="tooling" style="position:relative;"><a href="#tooling" aria-label="tooling permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Tooling</h2> <p>In terms of tooling the support is the same if not better than the ARM templates.</p> <h3 id="vs-code-extension" style="position:relative;"><a href="#vs-code-extension" aria-label="vs code extension permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>VS Code extension</h3> <p>VS Code comes with <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep" target="_blank" rel="nofollow noopener noreferrer">an official extension for Bicep</a>. This extension gives you validation, intellisense, dot property access, snippets etc.</p> <h3 id="cicd" style="position:relative;"><a href="#cicd" aria-label="cicd permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>CI/CD</h3> <p>If you’re using <a href="https://github.com/features/actions" target="_blank" rel="nofollow noopener noreferrer">GitHub Actions</a> for your CI/CD pipeline, there is already a <a href="https://github.com/marketplace/actions/bicep-build" target="_blank" rel="nofollow noopener noreferrer">Bicep action</a> created by our developer advocate <a href="https://github.com/justinyoo" target="_blank" rel="nofollow noopener noreferrer">Justin Yoo</a> which you can use to build you bicep file and deploy it to Azure.</p> <h3 id="cli" style="position:relative;"><a href="#cli" aria-label="cli permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>CLI</h3> <p>Bicep comes with a CLI that you can install locally on <a href="https://github.com/Azure/bicep/blob/main/docs/installing.md" target="_blank" rel="nofollow noopener noreferrer">Windows, MacOS, and Linux</a>. That gives you the ability to build and deploy your Bicep files with <a href="https://docs.microsoft.com/en-us/cli/azure" target="_blank" rel="nofollow noopener noreferrer">Azure CLI</a>.</p> <h2 id="summery" style="position:relative;"><a href="#summery" aria-label="summery permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summery</h2> <p>In short, I highly recommend using Bicep and improving your IaC and deployments. Of course if your ARM templates are too many, or very complex you might benefit from converting them more, but if you already have a streamlined pipeline with maintainable templates, you could keep them and create any new template using Bicep instead.</p><![CDATA[Exploring Azure Function Triggers and Bindings ⚡]]>https://yashints.dev/blog/2021/03/29/azure-function-bindingshttps://yashints.dev/blog/2021/03/29/azure-function-bindingsMon, 29 Mar 2021 00:00:00 GMT<p><a href="https://docs.microsoft.com/en-us/azure/azure-functions/" target="_blank" rel="nofollow noopener noreferrer">Azure Functions</a> is one of the the serverless services in Azure which allows you to run your business logic without worrying about where it’s running and how it scales. But it being serverless is not the highlight of this amazing service, the way it’s designed which allows you to leverage a very diverse set of triggers and input/output bindings without writing much code is to me the best of the best. So in this article I’ve decided to take you on a journey with a few of the common triggers and bindings and show you how to set them up quickly and without writing any unnecessary code.</p> <!--more--> <h2 id="working-with-azure-functions-locally" style="position:relative;"><a href="#working-with-azure-functions-locally" aria-label="working with azure functions locally permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Working with Azure Functions locally</h2> <p>Before we delve into the main topic of our discussion, I should quickly show you how easy it is to create and run these functions locally using a code editor or even a simple terminal.</p> <p>If you’re using <a href="https://code.visualstudio.com/" target="_blank" rel="nofollow noopener noreferrer">Visual Studio Code</a>, you need to install the <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions" target="_blank" rel="nofollow noopener noreferrer">Azure Functions Extensions</a>. For the command line approach, you’ll need the <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#v2" target="_blank" rel="nofollow noopener noreferrer">Azure Functions Core Tools</a> and either <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" target="_blank" rel="nofollow noopener noreferrer">Azure CLI</a> or <a href="https://docs.microsoft.com/en-us/powershell/azure/install-az-ps" target="_blank" rel="nofollow noopener noreferrer">Azure PowerShell</a> to create Azure resources. You can also use the <a href="https://azure.microsoft.com/downloads/" target="_blank" rel="nofollow noopener noreferrer">Visual Studio 2019</a> in which case you need to install its Azure Development component. It has built in templates for you to get started and be able to create functions and deploy them to Azure right there and then. You can find <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-your-first-function-visual-studio" target="_blank" rel="nofollow noopener noreferrer">the tutorial here</a>.</p> <p>The good news is that all of these tools are cross platform and compatible with Windows, Linux and MacOS.</p> <p>I’ll just show you one of them and leave the rest to you. After you have installed the necessary tools, open a terminal (I use <a href="https://docs.microsoft.com/en-us/windows/terminal/" target="_blank" rel="nofollow noopener noreferrer">Windows Terminal</a>) and initialize a new function using the init command:</p> <div class="gatsby-code-button-container" data-toaster-id="25155588810706320000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`func init myLocalFunctionProj --dotnet cd myLocalFunctionProj`, `25155588810706320000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">func init myLocalFunctionProj <span class="token parameter variable">--dotnet</span> <span class="token builtin class-name">cd</span> myLocalFunctionProj</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>You can use other languages too. For a list of supported languages please <a href="https://docs.microsoft.com/en-us/azure/azure-functions/supported-languages" target="_blank" rel="nofollow noopener noreferrer">visit the our documentation</a>.</p> <p>Now that you have a project you can create one or multiple functions in it:</p> <div class="gatsby-code-button-container" data-toaster-id="72635970050452060000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`func new --name HttpExample --template &quot;HTTP trigger&quot; --authlevel &quot;function&quot;`, `72635970050452060000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">func new <span class="token parameter variable">--name</span> HttpExample <span class="token parameter variable">--template</span> <span class="token string">"HTTP trigger"</span> <span class="token parameter variable">--authlevel</span> <span class="token string">"function"</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>This will create an HTTP triggered function for you which you can run using:</p> <div class="gatsby-code-button-container" data-toaster-id="68477181705774326000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`func start`, `68477181705774326000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">func start</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>Now you can send request to your function, so open a browser and visit <code class="language-text">http://localhost:7071/api/HttpExample?name=Yas</code>, the function should return the result and the browser should show you a <code class="language-text">Hello Yas</code> message. You’re now ready to explore triggers and binding.</p> <h2 id="triggers" style="position:relative;"><a href="#triggers" aria-label="triggers permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Triggers</h2> <p>There are quite a few triggers available which will cover almost any scenario you could think of. From a simple HTTP trigger to timer and blob, and a whole lot more are supported without you needing to write code. If I wanted to just quickly go through the most common ones, they are:</p> <table> <thead> <tr> <th>Type</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td><strong>Timer</strong></td> <td>Execute a function at a set interval.</td> </tr> <tr> <td><strong>HTTP</strong></td> <td>Execute a function when an HTTP request is received.</td> </tr> <tr> <td><strong>Blob</strong></td> <td>Execute a function when a file is uploaded or updated in Azure Blob storage.</td> </tr> <tr> <td><strong>Queue</strong></td> <td>Execute a function when a message is added to an Azure Storage queue.</td> </tr> <tr> <td><strong>Azure Cosmos DB</strong></td> <td>Execute a function when a document changes in a collection.</td> </tr> <tr> <td><strong>Event Hub</strong></td> <td>Execute a function when an event hub receives a new event.</td> </tr> <tr> <td><strong>Event Grid</strong></td> <td>Execute a function when an event is sent to Event Grid topics or queues.</td> </tr> </tbody> </table> <p>All of my examples will be using Csharp but you can use your language of choice. Examples of those can be found on <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?tabs=csharp" target="_blank" rel="nofollow noopener noreferrer">Azure Functions documentation site</a>.</p> <h3 id="timer-trigger" style="position:relative;"><a href="#timer-trigger" aria-label="timer trigger permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Timer trigger</h3> <p>Timer triggers allow you to run the function on a schedule. All you need is a <code class="language-text">TimerTrigger</code> attribute with its configuration:</p> <div class="gatsby-code-button-container" data-toaster-id="96038566687706990000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`[FunctionName(&quot;TimerTriggerCSharp&quot;)] public static void Run([TimerTrigger(&quot;0 */5 * * * *&quot;)]TimerInfo myTimer, ILogger log) { if (myTimer.IsPastDue) { log.LogInformation(&quot;Timer is running late!&quot;); } log.LogInformation(\$&quot;Csharp Timer trigger function executed at: {DateTime.Now}&quot;); }`, `96038566687706990000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FunctionName</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"TimerTriggerCSharp"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">TimerTrigger</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"0 */5 * * * *"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span><span class="token class-name">TimerInfo</span> myTimer<span class="token punctuation">,</span> <span class="token class-name">ILogger</span> log<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>myTimer<span class="token punctuation">.</span>IsPastDue<span class="token punctuation">)</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">LogInformation</span><span class="token punctuation">(</span><span class="token string">"Timer is running late!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> log<span class="token punctuation">.</span><span class="token function">LogInformation</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Csharp Timer trigger function executed at: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">DateTime<span class="token punctuation">.</span>Now</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>This function will run based on the <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=csharp#ncrontab-expressions" target="_blank" rel="nofollow noopener noreferrer">CRON expression</a> you have specified. Our example CRON expression will run the function 12 times an hour, every 5th minute of every hour of the day.</p> <h3 id="blob-trigger" style="position:relative;"><a href="#blob-trigger" aria-label="blob trigger permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Blob trigger</h3> <p>Blob triggers are very powerful and allow you to cover verity of scenarios from creating an image thumbnail on upload to check a file for virus.</p> <div class="gatsby-code-button-container" data-toaster-id="25619186228691260000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`[FunctionName(&quot;BlobTriggerCSharp&quot;)] public static void Run([BlobTrigger(&quot;container-name/{name}&quot;)] Stream myBlob, string name, ILogger log) { log.LogInformation(\$&quot;Csharp Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes&quot;); }`, `25619186228691260000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FunctionName</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"BlobTriggerCSharp"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">BlobTrigger</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"container-name/{name}"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name">Stream</span> myBlob<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name">ILogger</span> log<span class="token punctuation">)</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">LogInformation</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Csharp Blob trigger function Processed blob\n Name:</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">name</span><span class="token punctuation">}</span></span><span class="token string"> \n Size: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">myBlob<span class="token punctuation">.</span>Length</span><span class="token punctuation">}</span></span><span class="token string"> Bytes"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <blockquote> <p>💡 You will need the <code class="language-text">Microsoft.Azure.WebJobs.Extensions</code> package to use these attributes.</p> </blockquote> <h3 id="cosmos-db-trigger" style="position:relative;"><a href="#cosmos-db-trigger" aria-label="cosmos db trigger permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cosmos DB trigger</h3> <p>If you wanted to become aware when a document is inserted/updated in your Cosmos DB, you can use this trigger:</p> <div class="gatsby-code-button-container" data-toaster-id="83070115787007380000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`[FunctionName(&quot;CosmosTrigger&quot;)] public static void Run([CosmosDBTrigger( databaseName: &quot;ToDoItems&quot;, collectionName: &quot;Items&quot;, ConnectionStringSetting = &quot;CosmosDBConnection&quot;)]IReadOnlyList<Document> documents, ILogger log) { if (documents != null && documents.Count > 0) { log.LogInformation(\$&quot;Documents modified: {documents.Count}&quot;); log.LogInformation(\$&quot;First document Id: {documents[0].Id}&quot;); } }`, `83070115787007380000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FunctionName</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"CosmosTrigger"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CosmosDBTrigger</span><span class="token attribute-arguments"><span class="token punctuation">(</span> <span class="token named-parameter punctuation">databaseName</span><span class="token punctuation">:</span> <span class="token string">"ToDoItems"</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">collectionName</span><span class="token punctuation">:</span> <span class="token string">"Items"</span><span class="token punctuation">,</span> ConnectionStringSetting <span class="token operator">=</span> <span class="token string">"CosmosDBConnection"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span><span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>Document<span class="token punctuation">></span></span> documents<span class="token punctuation">,</span> <span class="token class-name">ILogger</span> log<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>documents <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> documents<span class="token punctuation">.</span>Count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">LogInformation</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Documents modified: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">documents<span class="token punctuation">.</span>Count</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> log<span class="token punctuation">.</span><span class="token function">LogInformation</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"First document Id: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">documents<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>Id</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>All you need is the connection string in your <code class="language-text">local.settings.json</code> in the connection string section:</p> <div class="gatsby-code-button-container" data-toaster-id="91237729633003980000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`{ &quot;IsEncrypted&quot;: false, &quot;Values&quot;: { &quot;AzureWebJobsStorage&quot;: &quot;<storage-connection-string>&quot; }, &quot;ConnectionStrings&quot;: { &quot;CosmosDBConnection&quot;: &quot;<cosmosdb-connection-string>&quot; } }`, `91237729633003980000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"IsEncrypted"</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">"Values"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"AzureWebJobsStorage"</span><span class="token operator">:</span> <span class="token string">"&lt;storage-connection-string>"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"ConnectionStrings"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"CosmosDBConnection"</span><span class="token operator">:</span> <span class="token string">"&lt;cosmosdb-connection-string>"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>For all the other examples you can update your settings file accordingly. I won’t be going through more triggers because I want to show the bindings. But you can find examples of all the supported triggers in our <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-trigger?tabs=csharp" target="_blank" rel="nofollow noopener noreferrer">reference documentation section</a>.</p> <h2 id="bindings" style="position:relative;"><a href="#bindings" aria-label="bindings permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Bindings</h2> <p>Bindings allow you to have input and output to/from your function out of the box. This is where magic happens and it’s super powerful. For example, if you wanted to have a function which is triggered by a queue and gets a blob as input, you will need this:</p> <div class="gatsby-code-button-container" data-toaster-id="52529296057922120000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`[FunctionName(&quot;BlobInput&quot;)] public static void Run( [QueueTrigger(&quot;queue-name&quot;)] string myQueueItem, [Blob(&quot;container/{queueTrigger}&quot;, FileAccess.Read)] Stream myBlob, ILogger log) { log.LogInformation(\$&quot;BlobInput processed blob\n Name:{myQueueItem} \n Size: {myBlob.Length} bytes&quot;); }`, `52529296057922120000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FunctionName</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"BlobInput"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Run</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">QueueTrigger</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"queue-name"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span></span> myQueueItem<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Blob</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"container/{queueTrigger}"</span><span class="token punctuation">,</span> FileAccess<span class="token punctuation">.</span>Read<span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name">Stream</span> myBlob<span class="token punctuation">,</span> <span class="token class-name">ILogger</span> log<span class="token punctuation">)</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">LogInformation</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"BlobInput processed blob\n Name:</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">myQueueItem</span><span class="token punctuation">}</span></span><span class="token string"> \n Size: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">myBlob<span class="token punctuation">.</span>Length</span><span class="token punctuation">}</span></span><span class="token string"> bytes"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>In this example the queue message contains the name of the blob. Or imagine people are uploading images into a blob container and you wanted an Azure Function to create thumbnails for you. You will need a blob trigger and a blob output:</p> <div class="gatsby-code-button-container" data-toaster-id="22465912551594824000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`[FunctionName(&quot;CreateThumbnail&quot;)] public static void Run( [BlobTrigger(&quot;images/{name}&quot;)] Stream image, [Blob(&quot;thumbnails/{name}&quot;, FileAccess.Write)] Stream thumbnail, ILogger log) { IImageFormat format; using (Image<Rgba32> input = Image.Load<Rgba32>(image, out format)) { input.Mutate(x => x.Resize(320, 200)); input.Save(output, format); } }`, `22465912551594824000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FunctionName</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"CreateThumbnail"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Run</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">BlobTrigger</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"images/{name}"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name">Stream</span> image<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Blob</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"thumbnails/{name}"</span><span class="token punctuation">,</span> FileAccess<span class="token punctuation">.</span>Write<span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name">Stream</span> thumbnail<span class="token punctuation">,</span> <span class="token class-name">ILogger</span> log<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">IImageFormat</span> format<span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token punctuation">(</span><span class="token class-name">Image<span class="token punctuation">&lt;</span>Rgba32<span class="token punctuation">></span></span> input <span class="token operator">=</span> Image<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Load</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Rgba32<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>image<span class="token punctuation">,</span> <span class="token keyword">out</span> format<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> input<span class="token punctuation">.</span><span class="token function">Mutate</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span><span class="token function">Resize</span><span class="token punctuation">(</span><span class="token number">320</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> input<span class="token punctuation">.</span><span class="token function">Save</span><span class="token punctuation">(</span>output<span class="token punctuation">,</span> format<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>The two image will share the same name, although the container name is different. For the name of the output blob, you could also use some pre-defined functions such as <code class="language-text">{rand-guid}</code> to generate a unique identifier or <code class="language-text">{DateTime}</code> to use current time as the name, although this might not match your requirement all the times.</p> <p>But what if you wanted to write it into the same container? In that case you couldn’t simply use the <code class="language-text">{name}</code> for your output binding. To generate the name of the output blob at runtime, you need to use imperative bindings.</p> <h3 id="ibinder" style="position:relative;"><a href="#ibinder" aria-label="ibinder permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>IBinder</h3> <p>Instead of using the <code class="language-text">[Blob]</code> attribute, you could use an instance of <code class="language-text">IBinder</code> interface which gives you much more control over your output binding and it’s name. The same example with a binder will look like:</p> <div class="gatsby-code-button-container" data-toaster-id="12529905136236906000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`using System; using System.IO; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Formats; using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace functions { public static class createThumbnail { [FunctionName(&quot;createThumbnail&quot;)] public static async Task Run( [BlobTrigger(&quot;images/original-{blobName}&quot;, Connection = &quot;AzureWebJobsStorage&quot;)] Stream image, string blobName, IBinder binder, ILogger log) { IImageFormat format; using (Image<Rgba32> input = Image.Load<Rgba32>(image, out format)) { input.Mutate(x => x.Resize(320, 200)); using (var writer = await binder.BindAsync<Stream>( new BlobAttribute(\$&quot;images/thumbnail-{blobName}&quot;, FileAccess.Write))) { input.Save(writer, format); } } } } } `, `12529905136236906000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="csharp"><pre style="counter-reset: linenumber NaN" class="language-csharp line-numbers"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">System</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>IO</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>Azure<span class="token punctuation">.</span>WebJobs</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Logging</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">SixLabors<span class="token punctuation">.</span>ImageSharp</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">SixLabors<span class="token punctuation">.</span>ImageSharp<span class="token punctuation">.</span>Processing</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">SixLabors<span class="token punctuation">.</span>ImageSharp<span class="token punctuation">.</span>Formats</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">SixLabors<span class="token punctuation">.</span>ImageSharp<span class="token punctuation">.</span>PixelFormats</span><span class="token punctuation">;</span> <span class="token keyword">namespace</span> <span class="token namespace">functions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">createThumbnail</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FunctionName</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"createThumbnail"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Run</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">BlobTrigger</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"images/original-{blobName}"</span><span class="token punctuation">,</span> Connection <span class="token operator">=</span> <span class="token string">"AzureWebJobsStorage"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name">Stream</span> image<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> blobName<span class="token punctuation">,</span> <span class="token class-name">IBinder</span> binder<span class="token punctuation">,</span> <span class="token class-name">ILogger</span> log<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">IImageFormat</span> format<span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token punctuation">(</span><span class="token class-name">Image<span class="token punctuation">&lt;</span>Rgba32<span class="token punctuation">></span></span> input <span class="token operator">=</span> Image<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Load</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Rgba32<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>image<span class="token punctuation">,</span> <span class="token keyword">out</span> format<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> input<span class="token punctuation">.</span><span class="token function">Mutate</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span><span class="token function">Resize</span><span class="token punctuation">(</span><span class="token number">320</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> writer <span class="token operator">=</span> <span class="token keyword">await</span> binder<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">BindAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Stream<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BlobAttribute</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"images/thumbnail-</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">blobName</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> FileAccess<span class="token punctuation">.</span>Write<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> input<span class="token punctuation">.</span><span class="token function">Save</span><span class="token punctuation">(</span>writer<span class="token punctuation">,</span> format<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <blockquote> <p>💡 The reason we added a suffix to the blob name is to prevent the infinite look when we write to the same container.</p> </blockquote> <h2 id="other-bindings" style="position:relative;"><a href="#other-bindings" aria-label="other bindings permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Other bindings</h2> <p>There are heaps of bindings available for you to leverage and create great solutions to solve your business problems and <a href="https://docs.microsoft.com/en-us/azure/azure-functions/" target="_blank" rel="nofollow noopener noreferrer">you can find out about them here</a>. All you need to do is to select the <code class="language-text">Reference > Triggers and bindings</code> from the left hand side.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>Hope this article has been interesting enough to intrigue you to go checkout Azure Functions and leverage their power to solve your business problems. Until next post, Sayōnara 👋🏽.</p><![CDATA[How to end-to-end test your Vue.js apps with Playwright 🧪]]>https://yashints.dev/blog/2021/02/07/e2e-vue-playwrighthttps://yashints.dev/blog/2021/02/07/e2e-vue-playwrightSun, 07 Feb 2021 00:00:00 GMT<p><a href="https://playwright.dev/" target="_blank" rel="nofollow noopener noreferrer">Playwright</a> is one of the recently released end to end testing frameworks which enables fast, reliable and capable automation and is cross platform. I really like it, but since it’s very easy to setup and the community around it is super cool, I like it even more.</p> <p>In this article I want to show you how you can write some tests for any <a href="https://vuejs.org/" target="_blank" rel="nofollow noopener noreferrer">Vue.js</a> application which is using <a href="https://auth0.com/" target="_blank" rel="nofollow noopener noreferrer">Auth0</a> as an identity provider. However, this could be used with any other provider too, since it covers the basics and makes you ready to write tests which cover different scenarios and user interactions.</p> <!--more--> <h2 id="concepts" style="position:relative;"><a href="#concepts" aria-label="concepts permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Concepts</h2> <p>Before we delve into the nitty gritty of things here, we should all agree on a few concepts:</p> <ul> <li><strong>End-to-end tests:</strong> End-to-end tests (AKA E2E) are like back box testing where you don’t test individual components or unit of code, instead you focus on testing a scenario end to end. With this type of tests, you use a real instance of the application. They are ideal for creating reliable and bug free application since they mimic user behaviour.</li> <li><strong>Vue.js:</strong> is a fantastic progressive frontend framework which is ideal for building user interfaces. It’s like a middle ground between Angular and React and is built from ground up with developers in mind. It’s easy to pickup and integrate with other libraries or existing projects.</li> <li><strong>Auth0:</strong> is an identity provider which has gained a really good reputation thanks to its complete solution which helps people secure their applications and add features like single sign on, multi-factor authentication and social media login to their applications.</li> </ul> <h2 id="stage-is-set" style="position:relative;"><a href="#stage-is-set" aria-label="stage is set permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stage is set</h2> <p>I have an application which is written in <em>Vue.js</em>. I have added authentication and authorization using <em>Auth0</em> and have different features shown/hidden to users based on their access levels.</p> <p>However, my unit and component tests don’t seem to cover some scenarios which our end users will do when interacting with our application. Some of this is because I have to use mocks when doing component testing, and unit tests don’t cover more than a piece of code.</p> <p>Now I need a way to test my application as if a user is sitting in front of their computer and uses our application. To achieve this, I will have to use end-to-end tests.</p> <h2 id="options" style="position:relative;"><a href="#options" aria-label="options permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Options</h2> <p>There are some great E2E test frameworks out there, and here are just a few:</p> <ul> <li>Protractor</li> <li>Nightwatch.js</li> <li>Cypress</li> <li>TestCafe</li> <li>Playwright</li> <li>WebdriverJS</li> <li>OpenTest</li> <li>Puppeteer</li> </ul> <p>And many more. However, I really like Playwright because it is easy to use and setup, it’s cross platform and integrates nicely with every CI/CD pipeline you’d think of.</p> <h2 id="the-code" style="position:relative;"><a href="#the-code" aria-label="the code permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The code</h2> <p>So I have an application which basically lists movies and people can buy tickets and go watch it in an imaginary gold cinema. The app also has an admin page where only users with administrator role can access. So let’s break through the code bit by bit:</p> <h3 id="main-setup" style="position:relative;"><a href="#main-setup" aria-label="main setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Main setup</h3> <p>In order for us to use the <em>Auth0</em> as a plugin with <em>Vue 3</em> we need to create a plugin and set it up in our main file. However, Vue 3 has changed the way we setup the plugins. So here is our little plugin (note code has been removed for brevity):</p> <div class="gatsby-code-button-container" data-toaster-id="2282009942550145000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`import createAuth0Client from '@auth0/auth0-spa-js'; let client; ///all the other methods and definitions export const setupAuth = async (options, callbackRedirect) => { client = await createAuth0Client({ ...options, }); try { if (window.location.search.includes('code=') && window.location.search.includes('state=')) { const { appState } = await client.handleRedirectCallback(); callbackRedirect(appState); } } //... return { install: app => { app.config.globalProperties.\$auth = authPlugin; }, }; }`, `2282009942550145000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">import</span> createAuth0Client <span class="token keyword">from</span> <span class="token string">'@auth0/auth0-spa-js'</span><span class="token punctuation">;</span> <span class="token keyword">let</span> client<span class="token punctuation">;</span> <span class="token comment">///all the other methods and definitions</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">setupAuth</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">options<span class="token punctuation">,</span> callbackRedirect</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> client <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">createAuth0Client</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>options<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'code='</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> window<span class="token punctuation">.</span>location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'state='</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> appState <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">handleRedirectCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">callbackRedirect</span><span class="token punctuation">(</span>appState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">//...</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token function-variable function">install</span><span class="token operator">:</span> <span class="token parameter">app</span> <span class="token operator">=></span> <span class="token punctuation">{</span> app<span class="token punctuation">.</span>config<span class="token punctuation">.</span>globalProperties<span class="token punctuation">.</span>$auth <span class="token operator">=</span> authPlugin<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>We also implement a route guard in the same file:</p> <div class="gatsby-code-button-container" data-toaster-id="26336409144695750000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`import { computed, watchEffect } from 'vue'; const authPlugin = { isAuthenticated: computed(() => state.isAuthenticated), loading: computed(() => state.loading), user: computed(() => state.user), popupOpen: computed(() => state.popupOpen), claims: computed(() => state.claims), getIdTokenClaims, getTokenSilently, getTokenWithPopup, handleRedirectCallback, loginWithRedirect, loginWithPopup, logout, getUser, }; export const routeGuard = (to, from, next) => { const { isAuthenticated, loading, claims } = authPlugin; const verify = () => { if (!isAuthenticated.value) { return next({ path: '/login', query: { returnUrl: to.path } }); } if (to?.meta?.authorize) { const roles = claims.value['http://schemas.microsoft.com/ws/2008/06/identity/claims/role']; if (roles.find(r => r === to.meta.authorize.role)) { return next(); } else { return next('/unauthorized'); } } }; if (!loading.value) { return verify(); } watchEffect(() => { if (loading.value === false && claims.value) { return verify(); } }); };`, `26336409144695750000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> computed<span class="token punctuation">,</span> watchEffect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> authPlugin <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">isAuthenticated</span><span class="token operator">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> state<span class="token punctuation">.</span>isAuthenticated<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">loading</span><span class="token operator">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> state<span class="token punctuation">.</span>loading<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">user</span><span class="token operator">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> state<span class="token punctuation">.</span>user<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">popupOpen</span><span class="token operator">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> state<span class="token punctuation">.</span>popupOpen<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">claims</span><span class="token operator">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> state<span class="token punctuation">.</span>claims<span class="token punctuation">)</span><span class="token punctuation">,</span> getIdTokenClaims<span class="token punctuation">,</span> getTokenSilently<span class="token punctuation">,</span> getTokenWithPopup<span class="token punctuation">,</span> handleRedirectCallback<span class="token punctuation">,</span> loginWithRedirect<span class="token punctuation">,</span> loginWithPopup<span class="token punctuation">,</span> logout<span class="token punctuation">,</span> getUser<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">routeGuard</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">to<span class="token punctuation">,</span> from<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> isAuthenticated<span class="token punctuation">,</span> loading<span class="token punctuation">,</span> claims <span class="token punctuation">}</span> <span class="token operator">=</span> authPlugin<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">verify</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isAuthenticated<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">'/login'</span><span class="token punctuation">,</span> <span class="token literal-property property">query</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">returnUrl</span><span class="token operator">:</span> to<span class="token punctuation">.</span>path <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>to<span class="token operator">?.</span>meta<span class="token operator">?.</span>authorize<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> roles <span class="token operator">=</span> claims<span class="token punctuation">.</span>value<span class="token punctuation">[</span><span class="token string">'http://schemas.microsoft.com/ws/2008/06/identity/claims/role'</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>roles<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">r</span> <span class="token operator">=></span> r <span class="token operator">===</span> to<span class="token punctuation">.</span>meta<span class="token punctuation">.</span>authorize<span class="token punctuation">.</span>role<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token string">'/unauthorized'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>loading<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">verify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">watchEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>loading<span class="token punctuation">.</span>value <span class="token operator">===</span> <span class="token boolean">false</span> <span class="token operator">&amp;&amp;</span> claims<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">verify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>This route guard might look intimidating at first glance, but all we’re doing is to create an object which exposes the Auth0 client methods, and then checks the route for a metadata property called authorize which holds the value of the role which should have access to the page.</p> <p>The rest is just checking whether they match and allow the redirect or send the user to the unauthorized page.</p> <p>In our main file:</p> <div class="gatsby-code-button-container" data-toaster-id="97340914586159280000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`import { createApp } from 'vue'; import router from './router'; import { setupAuth } from '@/auth/auth-plugin'; const authConfig = { domain: process.env.VUE_APP_DOMAIN, client_id: process.env.VUE_APP_CLIENTID, redirect_uri: process.env.VUE_APP_REDIRECT_URL, audience: process.env.VUE_APP_AUDIENCE, advancedOptions: { defaultScope: 'openid profile email crud:users', }, }; function callbackRedirect(appState) { router.push(appState && appState.targetUrl ? appState.targetUrl : '/'); } let app = createApp(App) .use(router); setupAuth(authConfig, callbackRedirect).then(auth => { app.use(auth).mount('#app'); });`, `97340914586159280000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> createApp <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> router <span class="token keyword">from</span> <span class="token string">'./router'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> setupAuth <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/auth/auth-plugin'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> authConfig <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">domain</span><span class="token operator">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">VUE_APP_DOMAIN</span><span class="token punctuation">,</span> <span class="token literal-property property">client_id</span><span class="token operator">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">VUE_APP_CLIENTID</span><span class="token punctuation">,</span> <span class="token literal-property property">redirect_uri</span><span class="token operator">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">VUE_APP_REDIRECT_URL</span><span class="token punctuation">,</span> <span class="token literal-property property">audience</span><span class="token operator">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">VUE_APP_AUDIENCE</span><span class="token punctuation">,</span> <span class="token literal-property property">advancedOptions</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">defaultScope</span><span class="token operator">:</span> <span class="token string">'openid profile email crud:users'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">callbackRedirect</span><span class="token punctuation">(</span><span class="token parameter">appState</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>appState <span class="token operator">&amp;&amp;</span> appState<span class="token punctuation">.</span>targetUrl <span class="token operator">?</span> appState<span class="token punctuation">.</span>targetUrl <span class="token operator">:</span> <span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> app <span class="token operator">=</span> <span class="token function">createApp</span><span class="token punctuation">(</span>App<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>router<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setupAuth</span><span class="token punctuation">(</span>authConfig<span class="token punctuation">,</span> callbackRedirect<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">auth</span> <span class="token operator">=></span> <span class="token punctuation">{</span> app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>auth<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mount</span><span class="token punctuation">(</span><span class="token string">'#app'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Here we’re simply creating an options object which is requied by the Auth0 SDK which has the client id, domain etc.</p> <p>And once that’s done, we will create our app but instead of using the plugin right away, we will call the <code class="language-text">setupAuth</code> which will then creates the client instance and returns the plugin instance. Now all we need to do is to call the <code class="language-text">.use</code> and use our plugin instance.</p> <h2 id="login-component" style="position:relative;"><a href="#login-component" aria-label="login component permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Login component</h2> <p>Now that we’ve got our auth plugin setup, it’s time to setup our login component. Fortunately it doesn’t require much code:</p> <div class="gatsby-code-button-container" data-toaster-id="57369245985930210000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`<div v-if=&quot;!user&quot;> <a href=&quot;#&quot; class=&quot;signup&quot; @click.prevent=&quot;login&quot;> You need to sign in first! </a> </div>`, `57369245985930210000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="html"><pre style="counter-reset: linenumber NaN" class="language-html line-numbers"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>!user<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>signup<span class="token punctuation">"</span></span> <span class="token attr-name">@click.prevent</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>login<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> You need to sign in first! <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>And in our component:</p> <div class="gatsby-code-button-container" data-toaster-id="44854511006129470000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`/// code removed for brevity export default { methods: { login: async function() { try { await this.\$auth.loginWithPopup(); const user = await this.\$auth.getUser(); const accessToken = await this.\$auth.getTokenSilently(); this.\$store.commit('SET_USER', user); //... } } } //... }`, `44854511006129470000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token comment">/// code removed for brevity</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span> <span class="token literal-property property">methods</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token function-variable function">login</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>$auth<span class="token punctuation">.</span><span class="token function">loginWithPopup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>$auth<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> accessToken <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>$auth<span class="token punctuation">.</span><span class="token function">getTokenSilently</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>$store<span class="token punctuation">.</span><span class="token function">commit</span><span class="token punctuation">(</span><span class="token string">'SET_USER'</span><span class="token punctuation">,</span> user<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//...</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">//...</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>The way this login works is that by clicking on the login button there would be a popup window opened from <em>Auth0</em> where the user enters their credentials and press submit.</p> <h2 id="router-config" style="position:relative;"><a href="#router-config" aria-label="router config permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Router config</h2> <p>And the last thing we would have here would be the routing configuration:</p> <div class="gatsby-code-button-container" data-toaster-id="1303742981738564900" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`import { createWebHistory, createRouter } from 'vue-router'; import { routeGuard } from '@/auth/auth-plugin'; //other imports export const routes = [ { path: '/', component: Home, }, //...other routes { path: '/login', component: Login, }, { path: '/admin', component: Admin, beforeEnter: routeGuard, meta: { authorize: { role: 'Admin', }, }, }, { path: '/unauthorized', component: UnAuthorized, }, ]; const router = createRouter({ history: createWebHistory(), routes, }); export default router;`, `1303742981738564900`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> createWebHistory<span class="token punctuation">,</span> createRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue-router'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> routeGuard <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/auth/auth-plugin'</span><span class="token punctuation">;</span> <span class="token comment">//other imports</span> <span class="token keyword">export</span> <span class="token keyword">const</span> routes <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">'/'</span><span class="token punctuation">,</span> <span class="token literal-property property">component</span><span class="token operator">:</span> Home<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">//...other routes</span> <span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">'/login'</span><span class="token punctuation">,</span> <span class="token literal-property property">component</span><span class="token operator">:</span> Login<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">'/admin'</span><span class="token punctuation">,</span> <span class="token literal-property property">component</span><span class="token operator">:</span> Admin<span class="token punctuation">,</span> <span class="token literal-property property">beforeEnter</span><span class="token operator">:</span> routeGuard<span class="token punctuation">,</span> <span class="token literal-property property">meta</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">authorize</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">role</span><span class="token operator">:</span> <span class="token string">'Admin'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">'/unauthorized'</span><span class="token punctuation">,</span> <span class="token literal-property property">component</span><span class="token operator">:</span> UnAuthorized<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">createRouter</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">history</span><span class="token operator">:</span> <span class="token function">createWebHistory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> routes<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> router<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>And that’s the basics of our application. Don’t worry I will put a link to the GitHub repo at the end so you would have all the code. I just want you to know at a really high level how the app is setup.</p> <h2 id="setting-up-the-tests" style="position:relative;"><a href="#setting-up-the-tests" aria-label="setting up the tests permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setting up the tests</h2> <p>In order to add the package to our app, we will do it via the CLI. So go ahead and execute below command in your terminal at the root of your client app:</p> <div class="gatsby-code-button-container" data-toaster-id="20742324209318298000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`vue add e2e-playwright --dev`, `20742324209318298000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">vue <span class="token function">add</span> e2e-playwright <span class="token parameter variable">--dev</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>It will take a while and a whole bunch of stuff happens behind the scene, but it does all the heavy lifting for you, create a folder for the E2E tests, and even creates a example test for your convenience. It adds <em>Playwright</em> so you can write tests, and <em>chai</em> to handle assertions.</p> <h2 id="writing-tests" style="position:relative;"><a href="#writing-tests" aria-label="writing tests permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Writing tests</h2> <p>Writing tests is the next step, for each test you have a few basic things to do. Import the necessary objects and methods:</p> <div class="gatsby-code-button-container" data-toaster-id="66158802764054310000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`const { chromium } = require('playwright'); const { expect } = require('chai');`, `66158802764054310000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> chromium <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'playwright'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> expect <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'chai'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>Here I am importing Chrome, but you have the option to use Safari or Firefox if you wish.</p> <p>Now we need some variables:</p> <div class="gatsby-code-button-container" data-toaster-id="53762544221167420000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`const baseUrl = 'http://localhost:8080/'; const adminPassword = 'Super_Secure_Pass'; const adminUserName = 'admin@example.com'; const normalUserName = 'user@example.com'; const normalUserPassword = 'Super_Secure_Pass';`, `53762544221167420000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">const</span> baseUrl <span class="token operator">=</span> <span class="token string">'http://localhost:8080/'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> adminPassword <span class="token operator">=</span> <span class="token string">'Super_Secure_Pass'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> adminUserName <span class="token operator">=</span> <span class="token string">'admin@example.com'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> normalUserName <span class="token operator">=</span> <span class="token string">'user@example.com'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> normalUserPassword <span class="token operator">=</span> <span class="token string">'Super_Secure_Pass'</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>I am just defining the passwords here to make it easier to understand, you make sure you have them in your environment files and use them that way so that you don’t commit user names and passwords into your source code.</p> <p>Now it’s time to write our tests, basically you need a describe method which is your test suite. In there you would need two variables for your browser and page instances:</p> <div class="gatsby-code-button-container" data-toaster-id="94055674234599640000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`describe('Authenticated Vue App: ', () => { let browser; let page; })`, `94055674234599640000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Authenticated Vue App: '</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> browser<span class="token punctuation">;</span> <span class="token keyword">let</span> page<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div> <p>Now you would need to create an instance of your browser and page. So go ahead and add a <code class="language-text">beforeEach</code> method. Inside that, lunch your browser, create a new page and navigate to your home page:</p> <div class="gatsby-code-button-container" data-toaster-id="8419483010305729000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`before(async () => { browser = await chromium.launch(); page = await browser.newPage(); await page.goto(baseUrl); });`, `8419483010305729000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token function">before</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> browser <span class="token operator">=</span> <span class="token keyword">await</span> chromium<span class="token punctuation">.</span><span class="token function">launch</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> page <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span><span class="token function">newPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">goto</span><span class="token punctuation">(</span>baseUrl<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Make sure you close these objects at the end of the tests via an <code class="language-text">after</code> method:</p> <div class="gatsby-code-button-container" data-toaster-id="97138346634436260000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`after(async () => { await page.close(); await browser.close(); });`, `97138346634436260000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token function">after</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div> <p>You’re now ready to write your first test. In this test we’re going to go to admin page without authentication and see what happens. Based on our router guard’s code, we know that the user should be redirected to login:</p> <div class="gatsby-code-button-container" data-toaster-id="14058504200565248000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`it('An unauthenticated user should not be able to see the admin page', async () => { await page.goto(\`\${baseUrl}admin\`); expect(page.url()).to.equal(\`\${baseUrl}login?returnUrl=/admin\`); });`, `14058504200565248000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'An unauthenticated user should not be able to see the admin page'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">goto</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>baseUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">admin</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">expect</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>to<span class="token punctuation">.</span><span class="token function">equal</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>baseUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">login?returnUrl=/admin</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div> <p>If you now run the tests by running <code class="language-text">yarn test:e2e</code>, you should see the test pass.</p> <h3 id="more-complicated-tests" style="position:relative;"><a href="#more-complicated-tests" aria-label="more complicated tests permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>More complicated tests</h3> <p>Now to add a spin on our test, say we wanted to actually login and see what happens. In this case we need to click on the login button, then find the opened window and fill in the username and password, then click on submit and come back to our app. This would require a bit more coding, but still easy to find out from <em>Playwright’s</em> documentation.</p> <p>First you would need to find the login button, then you need to use a <code class="language-text">Promise.all</code> method to be able to get a reference to your popup window:</p> <div class="gatsby-code-button-container" data-toaster-id="6586465777419593000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`const [popup] = await Promise.all([ page.waitForEvent('popup'), await page.click('a.signup') ]);`, `6586465777419593000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>popup<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span> page<span class="token punctuation">.</span><span class="token function">waitForEvent</span><span class="token punctuation">(</span><span class="token string">'popup'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">click</span><span class="token punctuation">(</span><span class="token string">'a.signup'</span><span class="token punctuation">)</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div> <p>Now that you have the reference, you need to fill in the info and click on the login:</p> <div class="gatsby-code-button-container" data-toaster-id="3275099176181517300" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`await popup.fill('input[type=&quot;email&quot;]', adminUserName); await popup.fill('input[type=&quot;password&quot;]', adminPassword); await popup.click('button[type=&quot;submit&quot;]');`, `3275099176181517300`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">await</span> popup<span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">'input[type="email"]'</span><span class="token punctuation">,</span> adminUserName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> popup<span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">'input[type="password"]'</span><span class="token punctuation">,</span> adminPassword<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> popup<span class="token punctuation">.</span><span class="token function">click</span><span class="token punctuation">(</span><span class="token string">'button[type="submit"]'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>And at last you need to make an assertion. Say you wanted to see whether an admin user will have access to the admin page. To do the assertion, you need to hook up to the close event of the popup window. So your test will look like:</p> <div class="gatsby-code-button-container" data-toaster-id="50367040573900490000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`it('be redirected back to admin page after login', async () => { await page.goto(\`\${baseUrl}admin\`); const [popup] = await Promise.all([ page.waitForEvent('popup'), await page.click('a.signup') ]); popup.on('close', async () => { expect(page.url()).to.equal(\`\${baseUrl}admin\`); }); await popup.fill('input[type=&quot;email&quot;]', adminUserName); await popup.fill('input[type=&quot;password&quot;]', adminPassword); await popup.click('button[type=&quot;submit&quot;]'); });`, `50367040573900490000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'be redirected back to admin page after login'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">goto</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>baseUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">admin</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>popup<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span> page<span class="token punctuation">.</span><span class="token function">waitForEvent</span><span class="token punctuation">(</span><span class="token string">'popup'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">click</span><span class="token punctuation">(</span><span class="token string">'a.signup'</span><span class="token punctuation">)</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> popup<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'close'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">expect</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>to<span class="token punctuation">.</span><span class="token function">equal</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>baseUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">admin</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> popup<span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">'input[type="email"]'</span><span class="token punctuation">,</span> adminUserName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> popup<span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">'input[type="password"]'</span><span class="token punctuation">,</span> adminPassword<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> popup<span class="token punctuation">.</span><span class="token function">click</span><span class="token punctuation">(</span><span class="token string">'button[type="submit"]'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>The reason why you’d need a <code class="language-text">waitForEvent</code> method in the <code class="language-text">Promise.all</code> method is that you need to wait for the popup window to be able to get a handle on it. Now if you run the tests again, they should all pass.</p> <h2 id="full-code" style="position:relative;"><a href="#full-code" aria-label="full code permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Full code</h2> <p>You can find the full source code on my <a href="https://github.com/yashints/vue-e2e-playwright" target="_blank" rel="nofollow noopener noreferrer">GitHub repository here</a>.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>And that’s how easy it is to write tests which mimic user interactions and can make you confident to ship reliable software. Happy testing and let me know what could automation have you done with Playwright if you got to that point 👋🏽👋🏽.</p><![CDATA[How much do you know about Azure CosmosDB Emulator? 🧐]]>https://yashints.dev/blog/2020/12/15/cosmos-emulatorhttps://yashints.dev/blog/2020/12/15/cosmos-emulatorTue, 15 Dec 2020 00:00:00 GMT<p><a href="https://azure.microsoft.com/en-au/services/cosmos-db/" target="_blank" rel="nofollow noopener noreferrer">Azure Cosmos DB</a> is one of the foundational services in Azure, which provides high availability, global scale and an impressive performance. However, it could be a bit costly for developers to spin up an instance during their development especially if they don’t know some of the basics such as what should be the partition key, what throughput they should select and so on that much.</p> <p><a href="https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator" target="_blank" rel="nofollow noopener noreferrer">The Azure Cosmos DB Emulator</a> is a service provided by Microsoft which allows you to emulate the Cosmos DB Service locally for development purposes. In addition, no matter whether you’re using Azure CosmosDB or not, at some point you might have to test some sort of DocumentDB locally.</p> <!--more--> <h2 id="intro" style="position:relative;"><a href="#intro" aria-label="intro permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Intro</h2> <p>Let’s be honest, there are other ways to work with Cosmos DB without paying any money such as a <a href="https://azure.microsoft.com/en-us/free/" target="_blank" rel="nofollow noopener noreferrer">Free Trial Account</a> which gives you <strong>$200 dollars</strong> to spend over a year, or even the <a href="https://www.documentdb.com/sql/demo" target="_blank" rel="nofollow noopener noreferrer">Cosmos DB Query Playground</a> which is an interactive website which helps you to learn the query syntax and provides you with a set of pre-defined JSON documents to work with.</p> <p>But eventually you either run out of money, or your product is live and your developers want to develop new features and fix bugs to be released very quickly. That’s when <em>Cosmos DB Emulator</em> really comes to the rescue. You have other advantages such as ability to work offline and or even have an Azure subscription.</p> <h2 id="features" style="position:relative;"><a href="#features" aria-label="features permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Features</h2> <h3 id="api-set" style="position:relative;"><a href="#api-set" aria-label="api set permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>API set</h3> <p>Azure Cosmos DB supports SQL API, as well as Table, MongoDB, Cassandra, and Gremlin which to me covers many scenarios from migrating an existing database from on-premise to Azure, modernising your legacy application, or create a brand new one. Azure Cosmos DB Emulator also supports these APIs although the underlying implementation details are different to the real service on Azure. In fact the emulator hides all abstractions from you and lets you focus on adding value to your team or organisation.</p> <blockquote> <p>💡 However keep in mind that the data explorer provided with the service <strong>only</strong> supports <strong>SQL API</strong> at the moment.</p> </blockquote> <h3 id="number-of-containers" style="position:relative;"><a href="#number-of-containers" aria-label="number of containers permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Number of containers</h3> <p>The emulator allows you to create a single account with multiple (up to 25 fixed sized, or 5 unlimited) containers.</p> <h3 id="operating-system" style="position:relative;"><a href="#operating-system" aria-label="operating system permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Operating System</h3> <p>At the moment, the emulator only supports 64-bit versions of Windows (Server 2012 R2, Server 2016, Server 2019, Windows 10) which demands a minimum of 2GB of RAM and 10GB of free disk space. You must also have administrative privileges to be able to install the client.</p> <p>In addition to that, it also comes with docker image which you can use to run the emulator in a docker container but only on windows containers. If you want to use docker in Linux or macOS, you will need to use it on a Windows virtual machine.</p> <h2 id="installation" style="position:relative;"><a href="#installation" aria-label="installation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Installation</h2> <p>Simply <a href="https://aka.ms/cosmosdb-emulator" target="_blank" rel="nofollow noopener noreferrer">download the executable from here</a> and install it on your Windows OS, if you’re running Linux or macOS or want to run it in a docker container please refer to <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=cli%2Cssl-netstd21#run-on-windows-docker" target="_blank" rel="nofollow noopener noreferrer">the official documentation</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/4de7592b693bf405cbfca0e60aeb55dc/d8309/em.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 81.85185185185185%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAQABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwAF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAdJERBnl/8QAGRAAAgMBAAAAAAAAAAAAAAAAABMBAgMQ/9oACAEBAAEFAk0E5iciO//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABoQAAICAwAAAAAAAAAAAAAAAACRAjIBIDP/2gAIAQEABj8CrhFIo5xWn//EABsQAAEEAwAAAAAAAAAAAAAAAAABEBEhceHx/9oACAEBAAE/IU08rqfE5diN/9oADAMBAAIAAwAAABBcz//EABYRAQEBAAAAAAAAAAAAAAAAAAEQEf/aAAgBAwEBPxAMn//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EAB4QAAIBAwUAAAAAAAAAAAAAAAERACEx0UFRYeHw/9oACAEBAAE/EMnwxoCzM9E3PJxA1RcACrqoxP/Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Cosmos DB Emulator" title="" src="/static/4de7592b693bf405cbfca0e60aeb55dc/47311/em.jpg" srcset="/static/4de7592b693bf405cbfca0e60aeb55dc/6f81f/em.jpg 270w, /static/4de7592b693bf405cbfca0e60aeb55dc/09d21/em.jpg 540w, /static/4de7592b693bf405cbfca0e60aeb55dc/47311/em.jpg 1080w, /static/4de7592b693bf405cbfca0e60aeb55dc/d8309/em.jpg 1165w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="differences" style="position:relative;"><a href="#differences" aria-label="differences permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Differences</h2> <p>As amazing it looks, there are a few differences you need to keep in mind when working with the emulator:</p> <ul> <li>As I mentioned before the data explorer only supports the SQL API. But you can use all other APIs from within your application using the SDK.</li> <li>It only supports a single account with a well known key. You can change the key from command line, but you cannot change it in the explorer.</li> <li>For now it only supports provisioning throughput mode only, meaning you can’t use it in serverless mode.</li> <li>It does not support core features such as different consistency levels, scaling, replication and performance guaranties.</li> <li>Since the client might not be up to date with Azure Cosmos DB service, make sure you always check the <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/estimate-ru-with-capacity-planner" target="_blank" rel="nofollow noopener noreferrer">Azure Cosmos DB capacity planner</a> to accurately estimate your required throughput.</li> <li>And the minimum ID property size is 256 characters.</li> </ul> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>In this short article I just showed you the tip of the iceberg about what the Azure Cosmos DB Emulator has to offer for you. It’s a great utility to be able to develop applications who rely on Azure Cosmos DB locally and has helped me in my pet projects which doesn’t even need to work with a Cosmos DB instance. Keep an eye out for more articles on some use cases this amazing tool can help you with.</p><![CDATA[Add dual screen support to your applications 💻]]>https://yashints.dev/blog/2020/12/07/dual-screenhttps://yashints.dev/blog/2020/12/07/dual-screenMon, 07 Dec 2020 00:00:00 GMT<p>If you have ever tried to add responsiveness to your web application you would know it’s difficult or at least takes a while to get it right. As progressive web apps (PWAs) are the first class citizens on many devices with the ability to get installed, it’s even more important to make sure user’s have the best experience possible. But technology advances in a much faster pace. We have now foldable devices like the shiny new <a href="https://devblogs.microsoft.com/surface-duo/microsoft-surface-duo-is-released/" target="_blank" rel="nofollow noopener noreferrer">Microsoft Surface Duo</a> which add more complexity to this equation.</p> <!--more--> <h2 id="problem" style="position:relative;"><a href="#problem" aria-label="problem permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Problem</h2> <p>What if you wanted to show your page across two screens without hiding the content under the middle edge? At least for now we don’t have any device with bending glass to be able to not worry about what appears where on the screen.</p> <p>In order to continue with this we need a demo application to make sure we’re on the same page. So let’s create one, say we have an image gallery which has 5 columns and someone is looking at it on their brand new Surface Duo:</p> <div class="gatsby-code-button-container" data-toaster-id="89479352997386290000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`<div class=&quot;gallery&quot;> <div class=&quot;grid&quot;> <div class=&quot;card&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=5&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> <div class=&quot;card p-3&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=4&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> <div class=&quot;card&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=3&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> <div class=&quot;card&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=2&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> <div class=&quot;card&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=1&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> <div class=&quot;card&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=7&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> <div class=&quot;card&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=8&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> <div class=&quot;card&quot;> <img class=&quot;card-img-top&quot; src=&quot;https://picsum.photos/200/200?random=9&quot; alt=&quot;Card image cap&quot; /> <div class=&quot;card-body&quot;> <h5 class=&quot;card-title&quot;>Card title</h5> <p class=&quot;card-text&quot;> This card has supporting text below as a natural lead-in to additional content. </p> <p class=&quot;card-text&quot;> <small class=&quot;text-muted&quot;>Last updated 3 mins ago</small> </p> </div> </div> </div> </div>`, `89479352997386290000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="html"><pre style="counter-reset: linenumber NaN" class="language-html line-numbers"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>gallery<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>grid<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=5<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card p-3<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=4<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=3<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=2<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=1<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=7<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=8<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-img-top<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/200/200?random=9<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Card image cap<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-body<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Card title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> This card has supporting text below as a natural lead-in to additional content. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card-text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>small</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Last updated 3 mins ago<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>small</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>And in our CSS file we just put a 5 column layout for the card column like so:</p> <div class="gatsby-code-button-container" data-toaster-id="38928701102676165000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`.grid { display: grid; grid-template-columns: repeat(5, 1fr); grid-row: auto; }`, `38928701102676165000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>5<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">grid-row</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Don’t worry too much about the code, you can view the result <a href="https://pwa-dual-screen.glitch.me/" target="_blank" rel="nofollow noopener noreferrer">here on Glitch</a>. Now to test how this page looks like you either have to have the device handy, or if you don’t have it like me, just use the <a href="https://docs.microsoft.com/en-us/dual-screen/android/emulator/surface-duo-download?tabs=windows" target="_blank" rel="nofollow noopener noreferrer">Surface Duo Emulator</a> which I find really cool. For example, here is how the page looks like at the moment:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; " > <a class="gatsby-resp-image-link" href="/static/5ea050377c298584b4ebdbf76110ecc0/2bef9/before.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 64.81481481481481%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADM0lEQVR42mWTX0zbVRTHf6/GN190zkh0xvkyp9kSZ8iyB43RqNmf6BaXvanbg7olOiYpGzIrMgbI3MoKFtJ12v1RUGBQMuiftbQw0KkJ2jhwDgobUPpntPyglPb38d7bsAc9yTf33HPuOff81TweD6FQiIHBQYLiDAQC+P1+gsEgQ0NDhMNhTCYTFRUVjIyMKPnAwICyWYXP5yM4GKA/1I+2vLxMLp/HAPLiXFlZQclyOQVJDocDp9Op+IWFBaHPCM7AMArIGzn02RwrKQMtFosxNjpKZGJcYILZaJREIkF0Lsac0N2ZmaG6pobPzWYmp6aYmY0SSyQLzkQAup4RrvNEx3Ri4/No1zxXaakqpe7YJ9Qe/ZjwHyOcc7YLBydwdbuwNJ5l06bNbCkuxlx1gq62Szisp0jG4yKbLB3trZyzn8dma8Lb50Lz9nTwcvFzrH/6KSpLjxDwuCl6vIiitWuoKi+j9up11r72Di/sfo+aVi+/XvMw3NXOVxUmOi86KDv4LpdOfkDJvlc4VV2JFvJ0s2HdI2x59hkO7HuLC85vefKxh9n56jZMnx5mf/VZtDXrefCJjex4v4Q2u5XmL4/y0Z7XOW4q5cXnN7L10Qd4SNM4dPAQ2q3RMM2Wk7Q01GITqfx18yZ+n5froQCjY2P4+9zsfGM7u3e9TXvrj1z+rpm6L8qwnK7D5fbS3Gil8rNyzOXH6OvtRZOdTd5LsZTJqC7KYuuLS6LYS6rrkpq+sd3v8qLQpdK60BnqLu1S6QVlJ0mTY3J3epop0UFd15WT27fHGZ+IqKJLqq+v54zFovhEPEEkEmFadF+OVTKZZHJykmh0TtlqUhgXj9LpNJlMRs1jQoxFWvyaNwoRWq1W7HZ7YQ7Fu1RKZLRUyCAl7nLMpL30pclQZZQS8oG8L2ezZAWkTJKlwUpjk03x8eQ80cQ9kvNpUZqM+NRQWC2Xxn9ICuXmrG5JbUsv6zbvYMPWPZgburD3BDnd2c95zxChP//GyC6KhfiHoNvF7zd+/r/DVVLrmMtyuecGL+3az5t7P+RC9zA/tH3PFXcfgV9+49bUXYZ9nXQ6z/D18RKudPzEv3hkVs/3QJxYAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Initial state of the application across two folds" title="" src="/static/5ea050377c298584b4ebdbf76110ecc0/2bef9/before.png" srcset="/static/5ea050377c298584b4ebdbf76110ecc0/01bf6/before.png 270w, /static/5ea050377c298584b4ebdbf76110ecc0/07484/before.png 540w, /static/5ea050377c298584b4ebdbf76110ecc0/2bef9/before.png 1024w" sizes="(max-width: 1024px) 100vw, 1024px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>You can see right away that it’s not a good look, I mean who would be willing to spend a second on this site? So how do we fix it? I think we’re now on the same page, so let’s dive in.</p> <h2 id="solution" style="position:relative;"><a href="#solution" aria-label="solution permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Solution</h2> <p>But don’t you worry, we’ve got you covered by two brand new experimental APIs that will enable you to effectively lay your elements on the screen where you want them to be.</p> <ul> <li><a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Foldables/explainer.md" target="_blank" rel="nofollow noopener noreferrer">CSS screen-spanning media feature</a></li> <li><a href="https://github.com/webscreens/window-segments/blob/master/EXPLAINER.md" target="_blank" rel="nofollow noopener noreferrer">JavaScript window segments enumeration API</a></li> </ul> <p>The former along with a set of environment variables allows you to be aware of the geometry of the fold. The latter is an API which can be used alongside the media feature to work with non-DOM targets like Canvas 2D and WebGL.</p> <h2 id="detecting-display-regions" style="position:relative;"><a href="#detecting-display-regions" aria-label="detecting display regions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Detecting display regions</h2> <p>As I mentioned earlier, the <code class="language-text">**screen-spanning**</code> media feature allows you to be aware of the fact that the page is spanned across multiple display regions. But how do we use it? Let’s have a look at the syntax. At its core, you will have the ability to use the below media query:</p> <div class="gatsby-code-button-container" data-toaster-id="39061032481216240000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@media (screen-spanning: <value>) { }`, `39061032481216240000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">screen-spanning</span><span class="token punctuation">:</span> &lt;value><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>The allowed values are:</p> <ul> <li><strong>single-fold-vertical</strong>: which describes the state of the viewport, when it’s spanning across a single fold and the fold’s posture is vertical. This would mean the device is in double portrait mode like below:</li> </ul> <p align="center"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 450px; "> <a class="gatsby-resp-image-link" href="/static/99d6bb43a607c28986686147482caa73/20e5d/double-portrait.jpg" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.96296296296296%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAEDAgX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAL/2gAMAwEAAhADEAAAAe3ma0XEF//EABsQAAEEAwAAAAAAAAAAAAAAAAEAAhAREhMh/9oACAEBAAEFAgONEbKWdJpsf//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABcQAAMBAAAAAAAAAAAAAAAAAAAQEYH/2gAIAQEABj8CNUip/8QAGxAAAgMAAwAAAAAAAAAAAAAAAAERIUExUWH/2gAIAQEAAT8hRChDTrQqmCXAoT5XZNej/9oADAMBAAIAAwAAABBDH//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxDGW//EABsQAQACAwEBAAAAAAAAAAAAAAEAESExcUFR/9oACAEBAAE/EGshUllhwZ7DY4F6OE9I0u4Ql1rt9bjMAXocJ//Z'); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="double portrait" title="" src="/static/99d6bb43a607c28986686147482caa73/20e5d/double-portrait.jpg" srcset="/static/99d6bb43a607c28986686147482caa73/6f81f/double-portrait.jpg 270w, /static/99d6bb43a607c28986686147482caa73/20e5d/double-portrait.jpg 450w" sizes="(max-width: 450px) 100vw, 450px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async"> </a> </span> <br> <i>Image from Microsoft Surface Duo blog</i> </p> <p>The code for this mode would be:</p> <div class="gatsby-code-button-container" data-toaster-id="23458042570962158000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@media (screen-spanning: single-fold-vertical) { /* styles applied in double-portrait (wide) mode */ }`, `23458042570962158000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">screen-spanning</span><span class="token punctuation">:</span> single-fold-vertical<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* styles applied in double-portrait (wide) mode */</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <ul> <li><strong>single-fold-horizontal</strong>: which describes the state of the viewport, when it’s spanning across a single fold but this time the posture is horizontal. This would match the double portrait tall mode like below:</li> </ul> <p align="center"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 307px; "> <a class="gatsby-resp-image-link" href="/static/b917f426d014c89db872985296381bbe/6bde8/double-portrait-tall.jpg" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 140.74074074074073%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAcABQDASIAAhEBAxEB/8QAGQAAAwADAAAAAAAAAAAAAAAAAAECAwQF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAgAB/9oADAMBAAIQAxAAAAHtOGssAyrFCtgA3//EABsQAAICAwEAAAAAAAAAAAAAAAACAREQEjEh/9oACAEBAAEFAqKgUujYTkYUbybkXn//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAYEQACAwAAAAAAAAAAAAAAAAAAARARIf/aAAgBAgEBPwHRlR//xAAbEAACAgMBAAAAAAAAAAAAAAABEAAhAjFxMv/aAAgBAQAGPwIs0Z5yR6j2UZtf/8QAHxABAAIBAwUAAAAAAAAAAAAAAQARMRAhQWFxobHh/9oACAEBAAE/IQKk5nSmL3l0Lt8kv8orTVb4Z7mos1mm8s//2gAMAwEAAgADAAAAEPM9A//EABcRAQEBAQAAAAAAAAAAAAAAAAABETH/2gAIAQMBAT8Q2JxqP//EABgRAAMBAQAAAAAAAAAAAAAAAAABMREh/9oACAECAQE/EGhRp9HT/8QAIBABAAIABQUAAAAAAAAAAAAAAQARITFhkdEQUXGx8f/aAAgBAQABPxC7E3LWafdgozIYQJMfwCvc+M5llKz7h1DL5IigC3VHE1uxxDQVVSqz/9k='); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="double portrait tall" title="" src="/static/b917f426d014c89db872985296381bbe/6bde8/double-portrait-tall.jpg" srcset="/static/b917f426d014c89db872985296381bbe/6f81f/double-portrait-tall.jpg 270w, /static/b917f426d014c89db872985296381bbe/6bde8/double-portrait-tall.jpg 307w" sizes="(max-width: 307px) 100vw, 307px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async"> </a> </span> <br> <i>Image from Microsoft Surface Duo blog</i> </p> <div class="gatsby-code-button-container" data-toaster-id="21148936908590030000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@media (screen-spanning: single-fold-horizontal) { /* styles applied in double-landscape (tall) mode */ }`, `21148936908590030000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">screen-spanning</span><span class="token punctuation">:</span> single-fold-horizontal<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* styles applied in double-landscape (tall) mode */</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <h2 id="environment-variables" style="position:relative;"><a href="#environment-variables" aria-label="environment variables permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Environment variables</h2> <p>To get the geometry of the device fold, you could leverage the browser environment variables. You have access to four environment variables:</p> <ul> <li>fold-top</li> <li>fold-left</li> <li>fold-width</li> <li>fold-height</li> </ul> <p>The picture below would nicely show you what there values represent:</p> <p align="center"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; "> <a class="gatsby-resp-image-link" href="/static/b7baf799e52328c2f552073e549b35e4/2bef9/env-variables.png" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 78.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB3ElEQVR42q2UW2/TQBCF8/9/QN+hqLRSH5om6iXlJipBqQkukAoqlQcQimsnjr2O92Z79zBrp3WilAdQIh3Fm3Pm02y8sx1s9qM7f3PKQkPwHFJwaK2hhKTnHFopWGtXsvYxYFEBibRIF5pkGneJIEmkuUTCFYKZQMQUEmEecq6mqBqkcUCzwJ/eaHRHgr4lTr5LnJIGN4rWAv3+GN1zhhe3mtaq9usMZQ+vBY6/qZpRGQLed9/7IsGoE6UVpGokpKa95+gd/MbLs4nbEbhsfZfNOEE/i6bDZeDhtcR0LjGngkw0YlzTfynQPQkweBPDGo2Ut77LxlTjdrYOHEnEmUROoblolAmNLJ2iexTgjIAgIOOt77KzfwWGwS8cHI03B4wn4812yJKIgP/RYY9+jFJBBc25q88ehZXiNXDwekovqAHc+y47YeJxYJ/eMlcaVVWgLBsVZUljINE/DvDq7cwdf+ii9SuSpCnqrQMNdjyGJ+9j7Hkxdi9b7XlTbO38wPbuT+z7CZ5/WPZiPKWaZ5dpPYA10D0ZY2wYRtbzR/bio0+6etA778oOP5GGvj2/GK54Tp7/1d6FoV3Mt+osj7jbgrsUluUuB8YYKcU8Y2sZt7bWrFwO6YbESNEfIWe+R5no2JIAAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="env variables" title="" src="/static/b7baf799e52328c2f552073e549b35e4/2bef9/env-variables.png" srcset="/static/b7baf799e52328c2f552073e549b35e4/01bf6/env-variables.png 270w, /static/b7baf799e52328c2f552073e549b35e4/07484/env-variables.png 540w, /static/b7baf799e52328c2f552073e549b35e4/2bef9/env-variables.png 1024w" sizes="(max-width: 1024px) 100vw, 1024px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async"> </a> </span> <br> <i>Image from Microsoft Surface Duo blog</i> </p> <h2 id="" style="position:relative;"><a href="#" aria-label=" permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a></h2> <p>So let’s see how we can use these features to improve our user experience a bit. First we know that we’re using the <strong><code class="language-text">single-fold-vertical</code></strong> mode, so we’ll add the relevant media query and play with the column count trying to either put the columns in the left or the right side of the fold:</p> <div class="gatsby-code-button-container" data-toaster-id="56358940640027200000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@media (screen-spanning: single-fold-vertical) { .card-columns { grid-template-columns: repeat(4, calc( env(fold-left) /2 - 10px) ); } }`, `56358940640027200000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">screen-spanning</span><span class="token punctuation">:</span> single-fold-vertical<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card-columns</span> <span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>4<span class="token punctuation">,</span> <span class="token function">calc</span><span class="token punctuation">(</span> <span class="token function">env</span><span class="token punctuation">(</span>fold-left<span class="token punctuation">)</span> /2 - 10px<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div> <blockquote> <p>💡 In order to access these features, you must enable experimental platform features on Edge by visiting <strong><code class="language-text">edge://flags/#enable-experimental-web-platform-features</code></strong> and enabling them.</p> </blockquote> <p>After setting the column count to 4, we will end up with something like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; " > <a class="gatsby-resp-image-link" href="/static/c3660014b26c5df5cdd7fd768b727d33/2bef9/semi-aligned.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 64.81481481481481%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADZUlEQVR42jWTf0zUZRzHv1tbf2T/9l+mJisn5aJEslqb0A9NTGlDREFOLGuSjUlsmjLzlF/eQqh2XB3e8dPZBNoyaWvQwgx/wRXcsk4QbhcH3HHHwf3gfvA9Xj3PN/1snz3vPfs878/z7PN6FJfLhd/vJxgKEpif17TP5yMQCBAOh5FhNBppamrStNxfWFhgXtQ+TK/Xq52ZmZlBCYdDLC5GiMaiRKNRTUsjqWOxmGZiNptpbW3VdDAUIhaPs7S0pKWqqlqd1MlkEsXnD3B/bAL35BRu9zTT016RM/w7Ocnk1BQhYW4ymTAYDMzNzeFw3GN0dJR4PCaaLxISDSKRiLZKY2XYs0RFf5D6a37aBjxcsgUZcTiZmJhgfMIpnjHL10YTac8/R9lHOs6U5FC0awt11Xr27MnlmXUpbExPY9WalZwz1KJcv+chz2wjr8vJjtb77PxikLauq9hH7IyOjTNw4xYVp/Ssfmol+bu2c7xoK3lvbORE2RFey87h8bUbWJWRxWNr1nOyShj+5XBQpS8n9/xlHvlskCf3X6b2fCN37gwy9Ied6poaig9+wLNrV3NIt5sDeW+hy93KyfJPeK9gH2mvbCIr+x0yXn2J+i/rUMb+GaKtsYLO9gZSCit5QhgePa6nWLePko9LyMx8ndTUdbycsZnaSj3HSos5fGAvJz4tJX1TGoqisGLFo9paVlaK4p50cvPGdZxjw3R2f0dDRz9Xf/qZczVnBS5f0d7WQkHhXj4/XcXdv8e5PXAT+6ANu20Ic9O36M+epr6hjsrqM/T90ociRz0762MusCCmFCepJgQCqmAtKPCJaKi0tLRgsVhQBRqSQ4mOrJFTlTqeSJAQuby8jKKqS8LQi8fjxS+wkIzFRc4KUOWejObmZi5YLqCKAx4BsURJTaoaKl5xGQm1ZFfjULomH6TUMiSksrP8OdoNpaG1+X+w54MkEyrLAuhoNEbkwSd4CLmyGAnya4+VW/3d/Hm7F/+si2/MJjK3ZVF06CD5ugLWv5BKYXY+3aV67oqB2Roacff2sS1nO+lbNvCu7k1SXnyaSkMlSiQUoLfnIr3XRvjtd5v4yzNc6fkRo6kRi9WCtdnK2zt3UCPQcf3wPc7Oi7i7OogMD1B+7AjvH97N0VMfsl+XQ3uHhf8AA7EptbMAzswAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Half aligned with columns reducing from 5 to 4" title="" src="/static/c3660014b26c5df5cdd7fd768b727d33/2bef9/semi-aligned.png" srcset="/static/c3660014b26c5df5cdd7fd768b727d33/01bf6/semi-aligned.png 270w, /static/c3660014b26c5df5cdd7fd768b727d33/07484/semi-aligned.png 540w, /static/c3660014b26c5df5cdd7fd768b727d33/2bef9/semi-aligned.png 1024w" sizes="(max-width: 1024px) 100vw, 1024px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>So we’re one step close to have a better view, but we have the two middle columns right on the fold, and the title is spanning across too. Let’s fix the title first. We could simply set a width for it to be in the first fold, or we could give it a margin right calculating where the fold is located. Let’s use the margin solution.</p> <div class="gatsby-code-button-container" data-toaster-id="84795445177694290000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`h1 { margin-right: calc( 100vw - (env(fold-left) + env(fold-width)) ); }`, `84795445177694290000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token selector">h1</span> <span class="token punctuation">{</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span> 100vw - <span class="token punctuation">(</span><span class="token function">env</span><span class="token punctuation">(</span>fold-left<span class="token punctuation">)</span> + <span class="token function">env</span><span class="token punctuation">(</span>fold-width<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>Here we’re calculating the width of the second fold and setting that as margin right for our header. Of course there are many other ways to achieve this, but I wanted to demonstrate the use of environment variables. After applying the above styles, we will end up with below, which is one step ahead to have a better UX.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; " > <a class="gatsby-resp-image-link" href="/static/4dccb45080ad712cba8ff5e125560c2a/2bef9/aligned.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 64.81481481481481%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADTElEQVR42n2TXWxTZRjHz50x4cYbryRcIUYdUSLLiCQmfmTG8bE5XFyjbpqAkZgwx8bYCAQTCSbMqVHrate1tVpxTFQ6yigbZQKyqUO2lanRQem6fq09/e45Xdv9PO+JF1z5T568/+e8z/95npP8XykQCJBIJMhkMyRTKZ3H43GSySS5XA4Bo9GI2WzWubhPaXV3RywW0zWRSARJiAqFAoqq6iF4NptDUVRULRcQzex2u87z+bz+/e4QPRRFoVwuI8myjN/vJxhcYjEYJBQKEY0tE47GiGihqEX6TSacTqfeUIj+D1JCTjLn+4N/Fm6zcDvAUihCsZAnKy9TyKT1IssX/fQPWhGtoukMy9ofpLWNookUi7EEwWWZQCRBMptH8oz9xPM7Wmnd3cX+/Ud5v3cA19BXeC3Hmf6mj8BFB++9ZaCpex/GyVFOXBim2+XEOnmJJwwHkGqaua/2TaTHd9Hx6ddIP5xxs2XL0xjq62nc1kBLy14cVjOnB07w+xk7U99+RGv9Mxw50Mz5sx/ymfEQp5xHuHblJI80tiE9uo01TzYjra/lnd5BpPMeLy9tf5HXGxuoq91OR3sPVtPH9PXs5qqzj6uOYxh2PEV7ax29h9/mDcPLHNprYMploqa5nTVrq1n34FakDc/S+YEFacTlpmrjZqqrt7Lp4Sp27Wzi6LHDvPpKLe17dtKxp46azevZ8FAVLQ11NL3wHJseq+ZgZ5dW+xqSdD/33rOWdQ9spOf4J0iLgUW+Gz7NyIgbj2eciUsTTP72C0M/DjN6wc3MzK90dnXQfbCb+emfmbg4hndsjLkbNzh3bhzbl0MMf+/m5CkX07PzSJVKRfNdVvfTysqKbgsRmUxW96OA8KDNZtN5Ll9ALRYpa7qiduZzeUqaTmhXV1eRSqWS5vAooXAY4UlRpKiK5sEoce1VCFitViwDZlaKqu5TtaANWq2Q0oZGYnFiCZlsXhi7om2odS39t5WYICDyVCqNz+fTc4fDgcliR9wmswWCKZVoRkFJyyQjS6SjQUo5mUpZRRKCmzdnmfB6GHLacLvPUqrA/J9/07avjYVbfswDg7zb+zm+sMro9TsMXvYzOhfmzl/Xmbk2zq1ZL3J4iqIS419hR0Qzjab1/wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Header aligned with using margin calculated based on fold size" title="" src="/static/4dccb45080ad712cba8ff5e125560c2a/2bef9/aligned.png" srcset="/static/4dccb45080ad712cba8ff5e125560c2a/01bf6/aligned.png 270w, /static/4dccb45080ad712cba8ff5e125560c2a/07484/aligned.png 540w, /static/4dccb45080ad712cba8ff5e125560c2a/2bef9/aligned.png 1024w" sizes="(max-width: 1024px) 100vw, 1024px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Now is the time to fix the two middle columns. One way to fix the middle column problem is to add an empty column which has no content. But it’s easier said than done. If you’re willing to play with the width of the columns, you can get away with something like this:</p> <div class="gatsby-code-button-container" data-toaster-id="79395705558282490000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`@media (screen-spanning: single-fold-vertical) { .grid { grid-template-columns: repeat(4, 23%); grid-gap: 25px; } .card:nth-child(4n + 2) { margin-right: 5px; } .card:nth-child(4n + 3) { margin-left: 10px; } }`, `79395705558282490000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">screen-spanning</span><span class="token punctuation">:</span> single-fold-vertical<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>4<span class="token punctuation">,</span> 23%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">grid-gap</span><span class="token punctuation">:</span> 25px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card:nth-child(4n + 2)</span> <span class="token punctuation">{</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card:nth-child(4n + 3)</span> <span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>This will result in:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; " > <a class="gatsby-resp-image-link" href="/static/3a6fefcc61860b93d168b01cf2a90650/2bef9/half-aligned.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 64.81481481481481%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADF0lEQVR42n2TTUxUVxTH38q46Zak7hoauyDRjcFFAyHR1LQaacrChQlpm5J+LHQlKqEapQoRUgIxQxNghhmYSFEEhvAhhmDwYwA/OswosZHawjDO+IaZeW++38DMr/e+icRuepKbe+557/7vOff8ruL3+9nc3CSeSBCNaUSEL9exWIxkMok0i8VCd3e36UciETRN+89QVdXcEwqFUOSmdDpNJps1RyqVMoUymQxZsZbW3dNDX1+f6adSxW/vj4RIRmpsbW2haLqO3/+GYEgl9LY4ZHbyxHA4TC63hc1mw+l0moLb29v8n4kM4wQ2/iYUXBeiAVGSStrIkcgaZMQszdHfL0ruknLiMJVoVCWux1h9tcry8gt8vhW8nuciFkdJ6wFW/xjFe38AbW0Osq8ZujNHs+Mmw48e83xTo6nDQsvVs2z4H+PxzLC0NMqi+w4VByrYvetD9pR8zOH9nzAxOony0rPAb20NDNvbmL/VytqTEa5ct3P8pwYaLU6ssx6+b2jGbu2Egrj4gJfA2gK+5YfUffk5ZaVlHK6q5Ny3NYwPj6N43XN0tZzF0vYzltZzPLh7i3ZbM1/UVfJ141d811pL1YlDdHVeJhxc5Kl7ENfNFqYnnNTXnaTm6FEunz9N7bEjuEzBZwtcb7vA1Yv1NDWcYnrkBpd+/YHyYx9w/Ju9VP9YSvlnH9F+rZHQhpvlJ2PMTnZxd+p39u+roKRkL1UHK6j+tJyJsaliU9b/+Yuw+gYtGkaPRc3m+FY8vHr9J/GkRkdnB729PaLDOXRdwzAyApMUz556ePRQlO/xsbqygiY4ViQGmhYXYCfR4wlygiWJioxpMd3scr/ostVqFYJ5Aa+6A7zkNJ1Jm3NWEFEoFFAkjFHBXUwQL+HM5/MC0Jz5cuQrkGZ32OkVgtKkmPxHbo6LBCKRKJJlwzDMuCI/SCefL+zAWVznTfJNDh0OBgYGTP9dTO57f37nK8GgX4C9ji5Kdi8tMDM7Y5ZmGPK+iiXbbHZ+aTrDg3kXI8N2ZqaHBI+LDA4O0T/k4vbkPW64ZvEHw/wL8+Nxh5KXN5MAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Columns aligned on the two sides of the fold" title="" src="/static/3a6fefcc61860b93d168b01cf2a90650/2bef9/half-aligned.png" srcset="/static/3a6fefcc61860b93d168b01cf2a90650/01bf6/half-aligned.png 270w, /static/3a6fefcc61860b93d168b01cf2a90650/07484/half-aligned.png 540w, /static/3a6fefcc61860b93d168b01cf2a90650/2bef9/half-aligned.png 1024w" sizes="(max-width: 1024px) 100vw, 1024px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>You can see we’ve come a long way from having a completely spanned content to a neat ordered view. But that’s all have been using only the CSS media feature. Let’s see what does the Window Segment Enumeration API have to offer.</p> <h2 id="window-segments-enumeration-api" style="position:relative;"><a href="#window-segments-enumeration-api" aria-label="window segments enumeration api permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Window Segments Enumeration API</h2> <p>This API helps you to deal with non-DOM elements such as a canvas or WebGL objects. You can leverage the <code class="language-text">getWindowSegments()</code> function which is accessible on the window object and returns an array of one or more <em>DOMRect</em> representing the geometry of and position of each display region:</p> <div class="gatsby-code-button-container" data-toaster-id="16650381349012777000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`const segments = window.getWindowSegments(); // case 1: desktops, traditional touch screen devices, foldable device not spanned console.log(segments.length) // 1 // case 2: dual-screen and foldable console.log(segments.length) // 2`, `16650381349012777000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">const</span> segments <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getWindowSegments</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// case 1: desktops, traditional touch screen devices, foldable device not spanned</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>segments<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token comment">// 1</span> <span class="token comment">// case 2: dual-screen and foldable </span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>segments<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token comment">// 2</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>If you wanted to know the fold posture inside your JavaScript code, you can calculate them easily using below snippet:</p> <div class="gatsby-code-button-container" data-toaster-id="72655717662005600000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`function isSingleFoldHorizontal() { const segments = window.getWindowSegments(); if( segments.length !== 2 ) { return false; } if( segments[0].top < segments[1].top ) { return true; } return false; }`, `72655717662005600000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">isSingleFoldHorizontal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> segments <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getWindowSegments</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span> segments<span class="token punctuation">.</span>length <span class="token operator">!==</span> <span class="token number">2</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span><span class="token punctuation">(</span> segments<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>top <span class="token operator">&lt;</span> segments<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>top <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Here we’re simply retuning false if the segments are now two, then check whether the top value of segment 0 is less than segment 1 which means the device is in horizontal state. If not we’re returning false which means it’s vertical posture.</p> <h2 id="where-to-start" style="position:relative;"><a href="#where-to-start" aria-label="where to start permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Where to start?</h2> <p>You can access the CSS screen-spanning media features using the <a href="edge://flags/#enable-experimental-web-platform-features" target="_blank" rel="nofollow noopener noreferrer">experimental web platform feature flag</a>, and the JavaScript Window Segments Enumeration API is accessible by enabling the <a href="edge://flags/#enable-javascript-harmony" target="_blank" rel="nofollow noopener noreferrer">Experimental JavaScript flag</a>.</p> <p>You also have the option of using Origin Trials, where you can acquire tokens and safely experiment with these new primitives in production in exchange for providing feedback about the APIs. <a href="https://developer.microsoft.com/en-us/microsoft-edge/origin-trials/dual-screen-and-foldable-devices-css-and/detail/" target="_blank" rel="nofollow noopener noreferrer">Sign up for an Origin Trial if you’re interested in testing out these APIs!</a></p> <p><a href="https://glitch.com/edit/#!/pwa-dual-screen" target="_blank" rel="nofollow noopener noreferrer">PS: You can find all the the code on Glitch here.</a></p><![CDATA[How to use Azure Cosmos Emulator as a local MongoDb database 🌍]]>https://yashints.dev/blog/2020/11/30/az-cosmos-emulatorhttps://yashints.dev/blog/2020/11/30/az-cosmos-emulatorMon, 30 Nov 2020 00:00:00 GMT<p>I recently was trying to prepare a demo which involved me having a local MongoDb database. I reviewed a few options and was about to choose one when I remembered I have <a href="https://yas.fyi/azcosemu" target="_blank" rel="nofollow noopener noreferrer">Azure Cosmos DB Emulator</a> installed and Cosmos DB supports MongoDb APIs.</p> <!--more--> <h2 id="install-if-you-havent-already" style="position:relative;"><a href="#install-if-you-havent-already" aria-label="install if you havent already permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Install if you haven’t already</h2> <p>You will need below requirements to be able to install the emulator:</p> <ul> <li>Windows Server 2012 R2, Windows Server 2016, 2019 or Windows 8 and 10. Docker on Windows, Linux and macOS is also supported.</li> <li>64-bit OS.</li> <li>Minimum 2GB RAM.</li> <li>At least 10GB available hard disk space.</li> </ul> <p>If you’ve ticked all of above already, then head <a href="https://aka.ms/cosmosdb-emulator" target="_blank" rel="nofollow noopener noreferrer">over here and install the latest version</a>. If you stumbled upon any issues use <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/troubleshoot-local-emulator" target="_blank" rel="nofollow noopener noreferrer">the troubleshooting guide</a> to find out what’s happening.</p> <blockquote> <p>💡 Make sure you check for updates regularly since each new version might contain new features and bug fixes.</p> </blockquote> <h2 id="starting-the-emulator" style="position:relative;"><a href="#starting-the-emulator" aria-label="starting the emulator permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Starting the emulator</h2> <p>Normally you would use the Windows start menu to find your programs and start the application, however, for the emulator to support MongoDB APIs, you will need to pass the <code class="language-text">EnableMongoDbEndpoint</code> argument.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/4de7592b693bf405cbfca0e60aeb55dc/d8309/startingthecosmosdbemulator.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 81.85185185185185%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAQABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwAF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAdJERBnl/8QAGRAAAgMBAAAAAAAAAAAAAAAAABMBAgMQ/9oACAEBAAEFAk0E5iciO//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABoQAAICAwAAAAAAAAAAAAAAAACRAjIBIDP/2gAIAQEABj8CrhFIo5xWn//EABsQAAEEAwAAAAAAAAAAAAAAAAABEBEhceHx/9oACAEBAAE/IU08rqfE5diN/9oADAMBAAIAAwAAABBcz//EABYRAQEBAAAAAAAAAAAAAAAAAAEQEf/aAAgBAwEBPxAMn//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EAB4QAAIBAwUAAAAAAAAAAAAAAAERACEx0UFRYeHw/9oACAEBAAE/EMnwxoCzM9E3PJxA1RcACrqoxP/Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Starting Azure Cosmos DB Emulator" title="" src="/static/4de7592b693bf405cbfca0e60aeb55dc/47311/startingthecosmosdbemulator.jpg" srcset="/static/4de7592b693bf405cbfca0e60aeb55dc/6f81f/startingthecosmosdbemulator.jpg 270w, /static/4de7592b693bf405cbfca0e60aeb55dc/09d21/startingthecosmosdbemulator.jpg 540w, /static/4de7592b693bf405cbfca0e60aeb55dc/47311/startingthecosmosdbemulator.jpg 1080w, /static/4de7592b693bf405cbfca0e60aeb55dc/d8309/startingthecosmosdbemulator.jpg 1165w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>You have a few options here:</p> <ol> <li>Create a shortcut and pass the required arguments</li> <li>Use command line to start it</li> </ol> <p>I am going to use <a href="https://github.com/microsoft/terminal" target="_blank" rel="nofollow noopener noreferrer">Windows Terminal</a>. Run the below command to fire up the emulator with MongoDB support:</p> <div class="gatsby-code-button-container" data-toaster-id="68190665757595830000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`& 'C:\Program Files\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe' /EnableMongoDbEndpoint`, `68190665757595830000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token operator">&amp;</span> <span class="token string">'C:\Program Files\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe'</span> /EnableMongoDbEndpoint</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>You will be prompted to approve the emulator making changes to your system and once approved, you will see the emulator starting and the interface will open in your default browser.</p> <h2 id="getting-the-connection-string" style="position:relative;"><a href="#getting-the-connection-string" aria-label="getting the connection string permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Getting the connection string</h2> <p>The very first thing you’d see is the quick start which should look something like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/1ded639cfb17580528aea8c832a21acb/57b36/emulatorquickstart.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAwABBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAei+uDNH/8QAGhAAAgMBAQAAAAAAAAAAAAAAAgMAARMQM//aAAgBAQABBQJYVa8xmYRPjz//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAcEAABAwUAAAAAAAAAAAAAAAACADKRECIxUXH/2gAIAQEABj8CG0caTAhMGEHK/wD/xAAdEAABAgcAAAAAAAAAAAAAAAAAARARITFBobHx/9oACAEBAAE/IVziy1KXgcgYHT//2gAMAwEAAgADAAAAEOAP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHRABAAEDBQAAAAAAAAAAAAAAAQARYfAQQVHB0f/aAAgBAQABPxA+uVUK1oWnUh8hv41pn+Gv/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Azure Cosmos DB Emulator connection string page" title="" src="/static/1ded639cfb17580528aea8c832a21acb/47311/emulatorquickstart.jpg" srcset="/static/1ded639cfb17580528aea8c832a21acb/6f81f/emulatorquickstart.jpg 270w, /static/1ded639cfb17580528aea8c832a21acb/09d21/emulatorquickstart.jpg 540w, /static/1ded639cfb17580528aea8c832a21acb/47311/emulatorquickstart.jpg 1080w, /static/1ded639cfb17580528aea8c832a21acb/0047d/emulatorquickstart.jpg 1620w, /static/1ded639cfb17580528aea8c832a21acb/274e1/emulatorquickstart.jpg 2160w, /static/1ded639cfb17580528aea8c832a21acb/57b36/emulatorquickstart.jpg 2425w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>What you’ll need is the <em>Mongo Connection String</em>. Copy that you’ll be ready to kick start. I am going to use the Node sample from the quick start to continue the rest of this guide, but you can use this straight away in your current code.</p> <h2 id="creating-a-nodejs-web-api-project" style="position:relative;"><a href="#creating-a-nodejs-web-api-project" aria-label="creating a nodejs web api project permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Creating a Node.js Web API project</h2> <p>Open up a command prompt in a new folder and initiate a project:</p> <div class="gatsby-code-button-container" data-toaster-id="53941591170971120000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`npm init -y`, `53941591170971120000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">npm</span> init <span class="token parameter variable">-y</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>The next thing to do is to install the dependencies:</p> <div class="gatsby-code-button-container" data-toaster-id="47788469438264380000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`# linux or macOS touch app.js # Windows echo &quot;&quot; > app.js # install dependencies npm install express mongodb body-parser`, `47788469438264380000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token comment"># linux or macOS</span> <span class="token function">touch</span> app.js <span class="token comment"># Windows</span> <span class="token builtin class-name">echo</span> <span class="token string">""</span> <span class="token operator">></span> app.js <span class="token comment"># install dependencies</span> <span class="token function">npm</span> <span class="token function">install</span> express mongodb body-parser</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h3 id="the-app" style="position:relative;"><a href="#the-app" aria-label="the app permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The app</h3> <p>We would have a simple application, since this tutorial is not a Node.js or Express.js focused, I will just brush on some steps here. Feel free to go through and understand each of these at your own pace.</p> <div class="gatsby-code-button-container" data-toaster-id="5506122035429128000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`const Express = require(&quot;express&quot;); const BodyParser = require(&quot;body-parser&quot;); const MongoClient = require(&quot;mongodb&quot;).MongoClient; const ObjectId = require(&quot;mongodb&quot;).ObjectID; var app = Express(); app.use(BodyParser.json()); app.use(BodyParser.urlencoded({ extended: true })); app.listen(5000, () => {});`, `5506122035429128000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">const</span> Express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"express"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> BodyParser <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"body-parser"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> MongoClient <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"mongodb"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>MongoClient<span class="token punctuation">;</span> <span class="token keyword">const</span> ObjectId <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"mongodb"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>ObjectID<span class="token punctuation">;</span> <span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token function">Express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>BodyParser<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>BodyParser<span class="token punctuation">.</span><span class="token function">urlencoded</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">extended</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>What we have here is a simple web server which responds to <code class="language-text">http://localhost:5000</code>. All we’re doing is initializing the <code class="language-text">MongoClient</code> and a web server. Next thing we need to do is to establish a connection with MongoDB interface of our emulator:</p> <div class="gatsby-code-button-container" data-toaster-id="81589217696579060000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`const Express = require(&quot;express&quot;); const BodyParser = require(&quot;body-parser&quot;); const MongoClient = require(&quot;mongodb&quot;).MongoClient; const ObjectId = require(&quot;mongodb&quot;).ObjectID; const CONNECTION_URL = &quot;YOUR CONNECTION STRING&quot;; const DATABASE_NAME = &quot;cosmos_emulator_mongo&quot;; var app = Express(); app.use(BodyParser.json()); app.use(BodyParser.urlencoded({ extended: true })); var database, collection; app.listen(5000, () => { MongoClient.connect(CONNECTION_URL, { useNewUrlParser: true }, (error, client) => { if(error) { throw error; } database = client.db(DATABASE_NAME); collection = database.collection(&quot;celebrities&quot;); console.log(&quot;Connected to \`&quot; + DATABASE_NAME + &quot;\`!&quot;); }); });`, `81589217696579060000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">const</span> Express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"express"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> BodyParser <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"body-parser"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> MongoClient <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"mongodb"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>MongoClient<span class="token punctuation">;</span> <span class="token keyword">const</span> ObjectId <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"mongodb"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>ObjectID<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token constant">CONNECTION_URL</span> <span class="token operator">=</span> <span class="token string">"YOUR CONNECTION STRING"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token constant">DATABASE_NAME</span> <span class="token operator">=</span> <span class="token string">"cosmos_emulator_mongo"</span><span class="token punctuation">;</span> <span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token function">Express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>BodyParser<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>BodyParser<span class="token punctuation">.</span><span class="token function">urlencoded</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">extended</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> database<span class="token punctuation">,</span> collection<span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> MongoClient<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token constant">CONNECTION_URL</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">useNewUrlParser</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> client</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span> <span class="token punctuation">}</span> database <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token constant">DATABASE_NAME</span><span class="token punctuation">)</span><span class="token punctuation">;</span> collection <span class="token operator">=</span> database<span class="token punctuation">.</span><span class="token function">collection</span><span class="token punctuation">(</span><span class="token string">"celebrities"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Connected to `"</span> <span class="token operator">+</span> <span class="token constant">DATABASE_NAME</span> <span class="token operator">+</span> <span class="token string">"`!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>The next thing we do to make it simpler to run the app is to add a start script to our <code class="language-text">package.json</code>:</p> <div class="gatsby-code-button-container" data-toaster-id="50961737180493100000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`&quot;scripts&quot;: { &quot;start&quot;: &quot;node app.js&quot; }`, `50961737180493100000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"start"</span><span class="token operator">:</span> <span class="token string">"node app.js"</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>If you run <code class="language-text">npm start</code> now, you should see the message connected to <code class="language-text">Connected to 'cosmos_emulator_mongo'</code> printed on your console which means you’ve successfully connected to the MongoDb APIs.</p> <h3 id="seed-the-database" style="position:relative;"><a href="#seed-the-database" aria-label="seed the database permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Seed the database</h3> <p>First let’s create a JSON file which contains some celebrities 😎:</p> <div class="gatsby-code-button-container" data-toaster-id="72546755292583010000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`[{ &quot;age&quot;: 18, &quot;birth_place&quot;: &quot;Harij, Gujarat, India&quot;, &quot;birth_sign&quot;: &quot;Pisces&quot;, &quot;birth_year&quot;: &quot;1996&quot;, &quot;birthday&quot;: &quot;September 28&quot;, &quot;name&quot;: &quot;Daxeel Soni&quot;, &quot;occupation&quot;: &quot;Developer of this API. More: www.daxeelsoni.in&quot;, &quot;photo_url&quot;: &quot;http://daxeelsoni.in/images/me.jpg&quot; }, { &quot;age&quot;: 28, &quot;birth_place&quot;: &quot;Houston&quot;, &quot;birth_sign&quot;: &quot;Libra&quot;, &quot;birth_year&quot;: &quot;1987&quot;, &quot;birthday&quot;: &quot;September 28&quot;, &quot;name&quot;: &quot;Hilary Duff&quot;, &quot;occupation&quot;: &quot;TV Actress&quot;, &quot;photo_url&quot;: &quot;http://www.famousbirthdays.com/thumbnails/duff-hilary-medium.jpg&quot; }, { &quot;age&quot;: 38, &quot;birth_place&quot;: &quot;Columbia&quot;, &quot;birth_sign&quot;: &quot;Libra&quot;, &quot;birth_year&quot;: &quot;1977&quot;, &quot;birthday&quot;: &quot;September 28&quot;, &quot;name&quot;: &quot;Young Jeezy&quot;, &quot;occupation&quot;: &quot;Rapper&quot;, &quot;photo_url&quot;: &quot;http://www.famousbirthdays.com/thumbnails/jeezy-young-medium.jpg&quot; }, { &quot;age&quot;: 48, &quot;birth_place&quot;: &quot;Roanoke&quot;, &quot;birth_sign&quot;: &quot;Libra&quot;, &quot;birth_year&quot;: &quot;1967&quot;, &quot;birthday&quot;: &quot;September 28&quot;, &quot;name&quot;: &quot;Challen Cates&quot;, &quot;occupation&quot;: &quot;TV Actress&quot;, &quot;photo_url&quot;: &quot;http://www.famousbirthdays.com/thumbnails/cates-challen-medium.jpg&quot; }]`, `72546755292583010000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token punctuation">[</span><span class="token punctuation">{</span> <span class="token property">"age"</span><span class="token operator">:</span> <span class="token number">18</span><span class="token punctuation">,</span> <span class="token property">"birth_place"</span><span class="token operator">:</span> <span class="token string">"Harij, Gujarat, India"</span><span class="token punctuation">,</span> <span class="token property">"birth_sign"</span><span class="token operator">:</span> <span class="token string">"Pisces"</span><span class="token punctuation">,</span> <span class="token property">"birth_year"</span><span class="token operator">:</span> <span class="token string">"1996"</span><span class="token punctuation">,</span> <span class="token property">"birthday"</span><span class="token operator">:</span> <span class="token string">"September 28"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Daxeel Soni"</span><span class="token punctuation">,</span> <span class="token property">"occupation"</span><span class="token operator">:</span> <span class="token string">"Developer of this API. More: www.daxeelsoni.in"</span><span class="token punctuation">,</span> <span class="token property">"photo_url"</span><span class="token operator">:</span> <span class="token string">"http://daxeelsoni.in/images/me.jpg"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"age"</span><span class="token operator">:</span> <span class="token number">28</span><span class="token punctuation">,</span> <span class="token property">"birth_place"</span><span class="token operator">:</span> <span class="token string">"Houston"</span><span class="token punctuation">,</span> <span class="token property">"birth_sign"</span><span class="token operator">:</span> <span class="token string">"Libra"</span><span class="token punctuation">,</span> <span class="token property">"birth_year"</span><span class="token operator">:</span> <span class="token string">"1987"</span><span class="token punctuation">,</span> <span class="token property">"birthday"</span><span class="token operator">:</span> <span class="token string">"September 28"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Hilary Duff"</span><span class="token punctuation">,</span> <span class="token property">"occupation"</span><span class="token operator">:</span> <span class="token string">"TV Actress"</span><span class="token punctuation">,</span> <span class="token property">"photo_url"</span><span class="token operator">:</span> <span class="token string">"http://www.famousbirthdays.com/thumbnails/duff-hilary-medium.jpg"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"age"</span><span class="token operator">:</span> <span class="token number">38</span><span class="token punctuation">,</span> <span class="token property">"birth_place"</span><span class="token operator">:</span> <span class="token string">"Columbia"</span><span class="token punctuation">,</span> <span class="token property">"birth_sign"</span><span class="token operator">:</span> <span class="token string">"Libra"</span><span class="token punctuation">,</span> <span class="token property">"birth_year"</span><span class="token operator">:</span> <span class="token string">"1977"</span><span class="token punctuation">,</span> <span class="token property">"birthday"</span><span class="token operator">:</span> <span class="token string">"September 28"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Young Jeezy"</span><span class="token punctuation">,</span> <span class="token property">"occupation"</span><span class="token operator">:</span> <span class="token string">"Rapper"</span><span class="token punctuation">,</span> <span class="token property">"photo_url"</span><span class="token operator">:</span> <span class="token string">"http://www.famousbirthdays.com/thumbnails/jeezy-young-medium.jpg"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"age"</span><span class="token operator">:</span> <span class="token number">48</span><span class="token punctuation">,</span> <span class="token property">"birth_place"</span><span class="token operator">:</span> <span class="token string">"Roanoke"</span><span class="token punctuation">,</span> <span class="token property">"birth_sign"</span><span class="token operator">:</span> <span class="token string">"Libra"</span><span class="token punctuation">,</span> <span class="token property">"birth_year"</span><span class="token operator">:</span> <span class="token string">"1967"</span><span class="token punctuation">,</span> <span class="token property">"birthday"</span><span class="token operator">:</span> <span class="token string">"September 28"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Challen Cates"</span><span class="token punctuation">,</span> <span class="token property">"occupation"</span><span class="token operator">:</span> <span class="token string">"TV Actress"</span><span class="token punctuation">,</span> <span class="token property">"photo_url"</span><span class="token operator">:</span> <span class="token string">"http://www.famousbirthdays.com/thumbnails/cates-challen-medium.jpg"</span> <span class="token punctuation">}</span><span class="token punctuation">]</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>Now that we have our JSON file, let’s seed the database. Replace the initial connect code with this:</p> <div class="gatsby-code-button-container" data-toaster-id="44694522472383504000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`app.listen(5000, () => { MongoClient.connect( CONNECTION_URL, { useNewUrlParser: true, sslValidate: false, }, (error, client) => { if (error) { throw error; } database = client.db(DATABASE_NAME); collection = database.collection(&quot;celebrities&quot;); collection.estimatedDocumentCount({}, (erorr, numOfRecords) => { if (numOfRecords <= 0) { fs.readFile(&quot;./info.json&quot;, &quot;utf8&quot;, (err, data) => { if (err) { console.log(\`Error reading file from disk: \${err}\`); } else { // parse JSON string to JSON object const celebrities = JSON.parse(data); collection.insertMany(celebrities, (error, result) => { if (error) { console.log(\`Error in saving seed data: \${error}\`); } console.log(\`Seed data inserted into the database!\`); }); } }); } else { console.log(\`Connected to database\`); } }); } ); });`, `44694522472383504000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript">app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> MongoClient<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span> <span class="token constant">CONNECTION_URL</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">useNewUrlParser</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">sslValidate</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> client</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span> <span class="token punctuation">}</span> database <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token constant">DATABASE_NAME</span><span class="token punctuation">)</span><span class="token punctuation">;</span> collection <span class="token operator">=</span> database<span class="token punctuation">.</span><span class="token function">collection</span><span class="token punctuation">(</span><span class="token string">"celebrities"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> collection<span class="token punctuation">.</span><span class="token function">estimatedDocumentCount</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">erorr<span class="token punctuation">,</span> numOfRecords</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>numOfRecords <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fs<span class="token punctuation">.</span><span class="token function">readFile</span><span class="token punctuation">(</span><span class="token string">"./info.json"</span><span class="token punctuation">,</span> <span class="token string">"utf8"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Error reading file from disk: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>err<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// parse JSON string to JSON object</span> <span class="token keyword">const</span> celebrities <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> collection<span class="token punctuation">.</span><span class="token function">insertMany</span><span class="token punctuation">(</span>celebrities<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> result</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Error in saving seed data: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Seed data inserted into the database!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Connected to database</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <p>All we’re doing here is to get the number of records in the collection and if there is nothing, read the JSON file and write it to the collection.</p> <p>Don’t forget to add <code class="language-text">const fs = require('fs')</code> at the top with other require statements.</p> <p>Now if you run <code class="language-text">npm start</code> would should see the application start and then the <strong>seed data inserted</strong> message.</p> <h3 id="creating-the-endpoint" style="position:relative;"><a href="#creating-the-endpoint" aria-label="creating the endpoint permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Creating the endpoint</h3> <p>And now all we need is to add our get endpoint to be able to fetch our celebrities. Don’t forget to add it before you create your listener:</p> <div class="gatsby-code-button-container" data-toaster-id="13515757457019628000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`app.get(&quot;/api/celebrities&quot;, (request, response) => { collection.find({}).toArray((error, result) => { if(error) { return response.status(500).send(error); } response.send(result); }); });`, `13515757457019628000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript">app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/api/celebrities"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">request<span class="token punctuation">,</span> response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> collection<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> result</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> response<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="testing-the-application" style="position:relative;"><a href="#testing-the-application" aria-label="testing the application permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing the application</h2> <p>You’re now ready to get your celebrities via the API. Simply open a browser and head over to <code class="language-text">http://localhost:5000/api/celebrities</code> or just use the below command:</p> <div class="gatsby-code-button-container" data-toaster-id="59893236012640210000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`curl -X GET http://localhost:5000/api/celebrities`, `59893236012640210000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-X</span> GET http://localhost:5000/api/celebrities</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>In this guide we saw how to use Azure Cosmos DB Emulator with MongoDb API to have a local MongoDb available for our local development and proof of concepts. Hope this has helped you save some time and also the trouble of installing yet another software on your system (of course if you already are using the emulator 😊).</p> <p>Enjoy hacking and let us know what awesome projects you’re doing using the emulator.</p> <p>PS: You can find the finished code in my <a href="https://github.com/yashints/azure-cosmosdb-emulator-mongodb" target="_blank" rel="nofollow noopener noreferrer">GitHub repository here</a>.</p><![CDATA[Access your clipboard on your other devices 😍 📋]]>https://yashints.dev/blog/2020/11/13/copy-across-deviceshttps://yashints.dev/blog/2020/11/13/copy-across-devicesFri, 13 Nov 2020 00:00:00 GMT<p>How many times have you been working on multiple systems and realised you are trying to paste something which you have copied on the other system? Well with the help of this article you can now share your clipboard between your <strong>Windows</strong> and <strong>Mac</strong> devices 😎.</p> <!--more--> <h2 id="windows-to-windows" style="position:relative;"><a href="#windows-to-windows" aria-label="windows to windows permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Windows to Windows</h2> <p>If you have multiple windows devices, it’s very easy. Simply open your control panel and search for <em>Clipboard Settings</em>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <a class="gatsby-resp-image-link" href="/static/b63796ebac291e25c796b69897fe398b/7f09e/windows-clipboard.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 71.48148148148148%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAMF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAde86oC//8QAFxABAAMAAAAAAAAAAAAAAAAAAQAgQf/aAAgBAQABBQIJlP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABoQAQADAAMAAAAAAAAAAAAAAAEAEBExYZH/2gAIAQEAAT8hDv2ZoKAIcV//2gAMAwEAAgADAAAAEK/P/8QAFhEAAwAAAAAAAAAAAAAAAAAAARAR/9oACAEDAQE/EIF//8QAFhEAAwAAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/EKV//8QAGxAAAgIDAQAAAAAAAAAAAAAAABEBIRAxcUH/2gAIAQEAAT8QeWnZQJSWPAslTgz/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Opening clipboard settings on windows" title="" src="/static/b63796ebac291e25c796b69897fe398b/47311/windows-clipboard.jpg" srcset="/static/b63796ebac291e25c796b69897fe398b/6f81f/windows-clipboard.jpg 270w, /static/b63796ebac291e25c796b69897fe398b/09d21/windows-clipboard.jpg 540w, /static/b63796ebac291e25c796b69897fe398b/47311/windows-clipboard.jpg 1080w, /static/b63796ebac291e25c796b69897fe398b/7f09e/windows-clipboard.jpg 1156w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Once opened, set the <strong>Sync across devices</strong> option to on. If you want to prevent sensitive data to be synced and do it on a case by case basis, set the <strong>Never automatically sync text that I copy</strong> option to on instead.</p> <p><img src="/1c3782258e317f014eda43682c3642ad/clipboard-settings-windows.jpg" alt="Clipboard settings on windows"></p> <p>While you’re at it, turn clipboard history on too to have a beautiful clipboard history available by pressing <kbd>Win</kbd>+<kbd>V</kbd>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 433px; " > <a class="gatsby-resp-image-link" href="/static/674b46e8eac0424a0c58915840d6d2dd/3561e/clipboard-history.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 132.96296296296296%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAgADAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB9dLqYyhaBrlUf//EABsQAAICAwEAAAAAAAAAAAAAAAACATEQEiER/9oACAEBAAEFAtTUnk8k8GtKGvDX/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAFBABAAAAAAAAAAAAAAAAAAAAMP/aAAgBAQAGPwJP/8QAGxAAAgMBAQEAAAAAAAAAAAAAAAERIVEQYfD/2gAIAQEAAT8hXyRWFoViSZUkcZ5GNWXvaFnL/9oADAMBAAIAAwAAABAP3QD/xAAVEQEBAAAAAAAAAAAAAAAAAAARIP/aAAgBAwEBPxAj/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHhAAAwEAAgIDAAAAAAAAAAAAAAERITFRQWGBkfH/2gAIAQEAAT8Q8idoppxd1CKETkgRlxgltOk7hwyMN+4z5J+Bc13BiksfQqSxTD//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Windows clipboard history" title="" src="/static/674b46e8eac0424a0c58915840d6d2dd/3561e/clipboard-history.jpg" srcset="/static/674b46e8eac0424a0c58915840d6d2dd/6f81f/clipboard-history.jpg 270w, /static/674b46e8eac0424a0c58915840d6d2dd/3561e/clipboard-history.jpg 433w" sizes="(max-width: 433px) 100vw, 433px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h3 id="use-the-copied-text" style="position:relative;"><a href="#use-the-copied-text" aria-label="use the copied text permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Use the copied text</h3> <p>Now if you copy something into your clipboard, based on assuming you wanted to sync everything, simply paste, or open your clipboard history on the target device to see it.</p> <blockquote> <p>⚠️ If you selected never automatically sync option, you can press <kbd>Win</kbd>+<kbd>V</kbd> to open up your clipboard history and select what you want to be synced across your devices after copying the items.</p> </blockquote> <h3 id="additional-tip" style="position:relative;"><a href="#additional-tip" aria-label="additional tip permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Additional tip</h3> <p>If you choose to use clipboard history, make sure you pin items you use frequently to save yourself some more time too.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 429px; " > <a class="gatsby-resp-image-link" href="/static/5943b22bbb49e2802deb3e2cfd714760/09fc4/pin-history.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 134.8148148148148%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAIBAwUE/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAdfi0oSkYLWV1rAP/8QAHBAAAwABBQAAAAAAAAAAAAAAAAECAxARITFB/9oACAEBAAEFAtkTWWqfAumho8GLRn//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAaEAABBQEAAAAAAAAAAAAAAAAxAAECETAy/9oACAEBAAY/Aipc0wz/AP/EABsQAAIDAQEBAAAAAAAAAAAAAAABESExQVGh/9oACAEBAAE/IVS39i1SSSyd+GC7Ak4JuFmCmFovrN6LRBs//9oADAMBAAIAAwAAABCv6wz/xAAXEQADAQAAAAAAAAAAAAAAAAAAAREQ/9oACAEDAQE/EIR7/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/ECv/xAAcEAEBAAMAAwEAAAAAAAAAAAABEQAhMUFRYXH/2gAIAQEAAT8QngL0jWQFmVCqV81VPZktHgv7gBs6LueMRCO/W4wIuec5kISuGmm/M59awo0cyPRhJ0cz/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Pin items in clipboard history on windows" title="" src="/static/5943b22bbb49e2802deb3e2cfd714760/09fc4/pin-history.jpg" srcset="/static/5943b22bbb49e2802deb3e2cfd714760/6f81f/pin-history.jpg 270w, /static/5943b22bbb49e2802deb3e2cfd714760/09fc4/pin-history.jpg 429w" sizes="(max-width: 429px) 100vw, 429px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="mac-to-mac" style="position:relative;"><a href="#mac-to-mac" aria-label="mac to mac permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Mac to Mac</h2> <p>If you’re a Mac user and want to achieve the same, you can use the <a href="https://support.apple.com/en-au/HT209460#:~:text=On%20your%20Mac%3A%20Choose%20Apple,Handoff%2C%20then%20turn%20on%20Handoff." target="_blank" rel="nofollow noopener noreferrer">Universal Clipboard</a>.</p> <p>It’s really simple, like we saw on windows setup.</p> <h2 id="windowsmaclinux-to-macwindowslinux" style="position:relative;"><a href="#windowsmaclinux-to-macwindowslinux" aria-label="windowsmaclinux to macwindowslinux permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Windows/Mac/Linux to Mac/Windows/Linux</h2> <p>If you use two different operating systems and want to simply share your content across, nothing works better than <a href="https://pastebin.com/" target="_blank" rel="nofollow noopener noreferrer">PASTEBIN</a>.</p> <p>This is a web tool that allows you to share your content across different devices with ease.</p> <h2 id="warning" style="position:relative;"><a href="#warning" aria-label="warning permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Warning</h2> <p>When using third party software whether it’s application or web app, never share secrets or your personal information.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>We saw how to take some simple steps to increase our productivity and access any content we would like across our devices. Hope you benefit from this like I did and see you next time.</p><![CDATA[I've joined Microsoft as an Azure Technical Trainer 🗺️ 😍]]>https://yashints.dev/blog/2020/11/02/joining-microsofthttps://yashints.dev/blog/2020/11/02/joining-microsoftMon, 02 Nov 2020 00:00:00 GMT<p>The cat is out of the bag now, I’ve joined <a href="https://www.microsoft.com/" target="_blank" rel="nofollow noopener noreferrer">Microsoft</a> as an <strong>Azure Technical Trainer</strong>. Some of you might be wondering why, and if so, this post is for you. I’ve decided to write down why I’ve made this decision and a bit about my 15+ years journey from high school to where I am now.</p> <p>PS: We don’t have a Microsoft campus in Melbourne, but it didn’t prevent me photoshopping myself in 😜. Also, <strong>Satya Nadella</strong> was another motivation for me to join since we have a decent resemblance 😎.</p> <!--more--> <h2 id="background" style="position:relative;"><a href="#background" aria-label="background permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Background</h2> <p>I started to write small programs when my brother bought a PC back in 1998. It was running MS-DOS and the first program I wrote could print a pyramid with stars on the console and was written in <a href="https://en.wikipedia.org/wiki/QBasic#:~:text=QBasic%20is%20an%20integrated%20development,on%20demand%20within%20the%20IDE." target="_blank" rel="nofollow noopener noreferrer">QBasic</a>. From then, I was always amazed by people who write programs which can help others write their own, and working at <a href="https://www.microsoft.com/" target="_blank" rel="nofollow noopener noreferrer">Microsoft</a> became one of my dream jobs.</p> <p>Later on, I progressed through high school, pre-uni and then uni when I started my journey as a software engineer. I’ve started with <a href="https://en.wikipedia.org/wiki/C%2B%2B" target="_blank" rel="nofollow noopener noreferrer">C++</a>, <a href="https://en.wikipedia.org/wiki/Turbo_Pascal" target="_blank" rel="nofollow noopener noreferrer">Turbo Pascal</a>, <a href="https://en.wikipedia.org/wiki/FoxPro" target="_blank" rel="nofollow noopener noreferrer">FoxPro</a>, <a href="https://en.wikipedia.org/wiki/Delphi_(software)" target="_blank" rel="nofollow noopener noreferrer">Delphi</a>, <a href="https://en.wikipedia.org/wiki/Active_Server_Pages" target="_blank" rel="nofollow noopener noreferrer">ASP Classic</a>, <a href="https://en.wikipedia.org/wiki/PHP" target="_blank" rel="nofollow noopener noreferrer">PHP</a>, and many other languages and ended up with, and finally <a href="https://en.wikipedia.org/wiki/ASP.NET_MVC" target="_blank" rel="nofollow noopener noreferrer">ASP.Net MVC</a>.</p> <p>Throughout those years I became really interested to web development and have been active in that area so far. Of course that wasn’t the only thing I picked up, I have written windows desktop apps with <a href="https://en.wikipedia.org/wiki/ASP.NET_Web_Forms" target="_blank" rel="nofollow noopener noreferrer">ASP.Net web forms</a> and <a href="https://en.wikipedia.org/wiki/Spring_Framework" target="_blank" rel="nofollow noopener noreferrer">Java Spring</a>, <a href="https://en.wikipedia.org/wiki/Java_Card" target="_blank" rel="nofollow noopener noreferrer">Java Card applets</a> to work with HSM and physical security devices, ActiveX, and many more I can’t even remember, not because I didn’t like them, but because I was really into learning new things and there were too many out there.</p> <p>I wasn’t born in a wealthy family, so I had work summers and other free times to earn enough to go through my studies for the rest of the year. And we don’t have big companies represented in Iran, so working for Microsoft was became a wish in my bucket list.</p> <h2 id="post-uni" style="position:relative;"><a href="#post-uni" aria-label="post uni permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Post uni</h2> <p>When I graduated from University with a Master’s degree in Information Security, I was hired by one of the well known software companies who were involved in banking industry. From there, I had jobs as software engineer, team lead and line manager and eventually I became the software engineering lead for one of the private banks’s PSP (payment service provider) company. But throughout all those years, I was on the lookout for an opportunity to migrate to another country and continue my professional career there.</p> <h2 id="migrating-to-australia" style="position:relative;"><a href="#migrating-to-australia" aria-label="migrating to australia permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Migrating to Australia</h2> <p>We considered Canada, US, and a few countries in Europe, but ended up pinning Australia on the map because of a few different reasons which suited our lifestyle and goals. So we sold/gave away most of our household items, rented out the apartment and packed our suitcases, jumped on a plane and ended up in Melbourne, Australia in 2015. I don’t want to go through how we got the Skilled Visa because it’s simply too long and brings back many not so good memories.</p> <h2 id="first-job-here" style="position:relative;"><a href="#first-job-here" aria-label="first job here permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>First job here</h2> <p>I immediately started applying for jobs here and without surprise, I was getting many calls, but not an actual opportunity that I liked. After one week, I had to bring down my expectations and start from scratch. I applied for a mid-level developer at a company and got in. This was after 3 weeks from our arrival and believe it or not is a record time for migrants to get a job.</p> <h2 id="moving-into-consulting-and-public-speaking" style="position:relative;"><a href="#moving-into-consulting-and-public-speaking" aria-label="moving into consulting and public speaking permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Moving into consulting and public speaking</h2> <p>I didn’t stay there for long, as I didn’t want to waste any time following my goals. So I got into a consulting firm and eventually joined <a href="https://www.linkedin.com/company/readify/?originalSubdomain=au" target="_blank" rel="nofollow noopener noreferrer">Readify (one of the best consulting firms in Australia)</a> back in 2016. Because of my love for knowledge sharing, I got into public speaking starting from local meetups ended up at international conferences around the world. I am super proud of what I’ve achieved throughout these years and owe a big part to Readify and later on Telstra Purple. However, I still had my bucket list getting dust and wasn’t enjoying what I was doing as my full time job.</p> <h2 id="decision-to-shift-to-developer-advocacy" style="position:relative;"><a href="#decision-to-shift-to-developer-advocacy" aria-label="decision to shift to developer advocacy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Decision to shift to developer advocacy</h2> <p>I was doing conference talks, blogs, and was involved in development community which has been a real pleasure, but managing all of that while having a full time job was really tough. So I started to search for similar roles and came across developer advocacy. However, this not a role which is very well known in Australia apart from big companies like Microsoft, Amazon AWS, IBM etc and some smaller ones like Auth0 and Twilio. I had a few discussions with different people to get an idea of what’s required to be able to score one of those jobs, but didn’t get a chance to find and apply for one.</p> <h2 id="when-an-opportunity-presents-itself" style="position:relative;"><a href="#when-an-opportunity-presents-itself" aria-label="when an opportunity presents itself permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>When an opportunity presents itself</h2> <p>So I continued what I was doing, but still were on the lookout for something interesting which could help me do what I love to do. A few weeks back one of my friends who also works at Microsoft told me about a position on the Azure Technical Training team and I thought that’s something I would be really good at. It would be a bit of shift from web development, but the pros (working at <strong>Microsoft</strong>, sharing knowledge, and being close to what I love to do) convinced me to apply. I went through the interview and got an offer which made my year. This year has been really tough for me and my family and this event really helped us recover from these last few months of stress and anxiety.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>So here I am, employed by my dream company, and doing what I love to do. It couldn’t have ended up better than this, I’ve already talked to my manager and he knows and supports my passion to be involved in development community, so this doesn’t mean I won’t be active on conferences and blog posts anymore. This amazing opportunity will help me grow in many ways as working at Microsoft and working with so many talented and amazing people would.</p> <p>I just hope this has inspired a few of you fellow developers out there to never give up hope and follow your dreams however distant and impossible they seem 🤘🏽 💪🏽.</p><![CDATA[Get started with Vue 3 and Tailwindcss]]>https://yashints.dev/blog/2020/10/22/vue-tailwindhttps://yashints.dev/blog/2020/10/22/vue-tailwindThu, 22 Oct 2020 00:00:00 GMT<p>Last week I wanted to setup a <a href="https://v3.vuejs.org/" target="_blank" rel="nofollow noopener noreferrer">Vue.js v3</a> app with <a href="https://tailwindcss.com/" target="_blank" rel="nofollow noopener noreferrer">Tailwindcss</a> and although many articles exists for that, I couldn’t get it done. The fact is that you need to use <a href="">postcss</a> to get your setup working, but with new version of <strong>Vue</strong>, the <code class="language-text">postcss</code> config file is not picked up. So after a few try and errors, I finally got it working and thought to jot down what I went through to make it easier for my future self, and hopefully a few of my fellow developers around the world.</p> <!--more--> <h2 id="vue-cli" style="position:relative;"><a href="#vue-cli" aria-label="vue cli permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Vue CLI</h2> <p>You can setup your <strong>Vue</strong> project with simply importing the script tag and start coding, but I normally like to use <a href="https://cli.vuejs.org/" target="_blank" rel="nofollow noopener noreferrer">Vue CLI</a> to get started because it takes care of the many things for me and gives a really good starting point.</p> <p>So let’s start by installing Vue CLI if you don’t have it already:</p> <div class="gatsby-code-button-container" data-toaster-id="99794607296837730000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`npm install -g @vue/cli # OR yarn global add @vue/cli`, `99794607296837730000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">-g</span> @vue/cli <span class="token comment"># OR</span> <span class="token function">yarn</span> global <span class="token function">add</span> @vue/cli</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div> <p>This will install the Vue CLI for you and once that’s done, you’d be ready to create your project. If you already have CLI installed, make sure you update it first to get support for <em>Vue v3 preview</em>.</p> <h2 id="creating-the-project" style="position:relative;"><a href="#creating-the-project" aria-label="creating the project permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Creating the project</h2> <p>In order to create your project, you need to call the CLI and give your project name:</p> <div class="gatsby-code-button-container" data-toaster-id="1733699455177473000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`vue create vue-tailwindcss`, `1733699455177473000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash">vue create vue-tailwindcss</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <p>This command will start the wizard and asks you what version of Vue you want to use and what additional options you want to have.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 26.296296296296294%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAFABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAIDBf/EABQBAQAAAAAAAAAAAAAAAAAAAAL/2gAMAwEAAhADEAAAAcSYxaA//8QAFhAAAwAAAAAAAAAAAAAAAAAAARAx/9oACAEBAAEFAkJ//8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQMBAT8BZ//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAgEBPwGMf//EABQQAQAAAAAAAAAAAAAAAAAAABD/2gAIAQEABj8Cf//EABkQAQACAwAAAAAAAAAAAAAAAAEAEBEhUf/aAAgBAQABPyEc8gbq/9oADAMBAAIAAwAAABCH3//EABURAQEAAAAAAAAAAAAAAAAAAAEA/9oACAEDAQE/EEjDf//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxACSNv/xAAbEAEAAgIDAAAAAAAAAAAAAAABACERQVGRwf/aAAgBAQABPxB9OkED5GFjN8z/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Vue CLI wizard" title="" src="/static/f1d31a73e389807ff13ceff8a58b79b0/47311/cli-wizard.jpg" srcset="/static/f1d31a73e389807ff13ceff8a58b79b0/6f81f/cli-wizard.jpg 270w, /static/f1d31a73e389807ff13ceff8a58b79b0/09d21/cli-wizard.jpg 540w, /static/f1d31a73e389807ff13ceff8a58b79b0/47311/cli-wizard.jpg 1080w, /static/f1d31a73e389807ff13ceff8a58b79b0/bb656/cli-wizard.jpg 1297w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You can choose between default seetings, or making a completely custom setup. I will normally choose custom which gives me more freedom as to what I want to setup.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 28.148148148148145%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAGABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAIDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHGkVNYH//EABUQAQEAAAAAAAAAAAAAAAAAAABB/9oACAEBAAEFAkf/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAQ/9oACAEBAAY/An//xAAYEAACAwAAAAAAAAAAAAAAAAABEQAQYf/aAAgBAQABPyHYS0r/2gAMAwEAAgADAAAAEIAP/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAh/9oACAEDAQE/EB2f/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGhAAAgIDAAAAAAAAAAAAAAAAAAERMSFRkf/aAAgBAQABPxB1JYI9lRC0+n//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Vue CLI wizard" title="" src="/static/1ca1c81a1e093f75c09a6ec363398937/47311/cli-custom.jpg" srcset="/static/1ca1c81a1e093f75c09a6ec363398937/6f81f/cli-custom.jpg 270w, /static/1ca1c81a1e093f75c09a6ec363398937/09d21/cli-custom.jpg 540w, /static/1ca1c81a1e093f75c09a6ec363398937/47311/cli-custom.jpg 1080w, /static/1ca1c81a1e093f75c09a6ec363398937/0047d/cli-custom.jpg 1620w, /static/1ca1c81a1e093f75c09a6ec363398937/274e1/cli-custom.jpg 2160w, /static/1ca1c81a1e093f75c09a6ec363398937/ab590/cli-custom.jpg 2337w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>From here press <kbd>Entre</kbd> on <strong>Choose Vue version</strong>, and select <strong>3.x (Preview)</strong>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1080px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 16.296296296296294%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAADABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAMF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAcSxZIH/xAAXEAEAAwAAAAAAAAAAAAAAAAAAAQIx/9oACAEBAAEFAlU7/8QAFREBAQAAAAAAAAAAAAAAAAAAEEH/2gAIAQMBAT8Bp//EABURAQEAAAAAAAAAAAAAAAAAABBB/9oACAECAQE/AYf/xAAUEAEAAAAAAAAAAAAAAAAAAAAQ/9oACAEBAAY/An//xAAXEAADAQAAAAAAAAAAAAAAAAAAATER/9oACAEBAAE/IdZaLH//2gAMAwEAAgADAAAAEIgv/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAEx/9oACAEDAQE/EIXX/8QAFhEAAwAAAAAAAAAAAAAAAAAAARAx/9oACAECAQE/EDS//8QAGhAAAQUBAAAAAAAAAAAAAAAAAQAQESExQf/aAAgBAQABPxDK0ROVzxt//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Vue CLI wizard" title="" src="/static/4fd95e31fdc584eb3ee5c391f22342a4/47311/cli-vue3.jpg" srcset="/static/4fd95e31fdc584eb3ee5c391f22342a4/6f81f/cli-vue3.jpg 270w, /static/4fd95e31fdc584eb3ee5c391f22342a4/09d21/cli-vue3.jpg 540w, /static/4fd95e31fdc584eb3ee5c391f22342a4/47311/cli-vue3.jpg 1080w, /static/4fd95e31fdc584eb3ee5c391f22342a4/0047d/cli-vue3.jpg 1620w, /static/4fd95e31fdc584eb3ee5c391f22342a4/05ac4/cli-vue3.jpg 1753w" sizes="(max-width: 1080px) 100vw, 1080px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>I would normally choose <a href="https://www.typescriptlang.org/" target="_blank" rel="nofollow noopener noreferrer">TypeScript</a>, <a href="https://babeljs.io/" target="_blank" rel="nofollow noopener noreferrer">Babel</a>, Linter, Unit and E2E testing optiuons, but feel free to setup how you like.</p> <p>When it’s done go navigate into the folder or open it up with your editor of choice. Mince is <a href="https://code.visualstudio.com/" target="_blank" rel="nofollow noopener noreferrer">VS Code</a>.</p> <h2 id="installing-the-required-dependencies" style="position:relative;"><a href="#installing-the-required-dependencies" aria-label="installing the required dependencies permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Installing the required dependencies</h2> <p>At this point we need to install Tailwindcss:</p> <div class="gatsby-code-button-container" data-toaster-id="52672046323650900000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`npm install tailwindcss`, `52672046323650900000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="bash"><pre style="counter-reset: linenumber NaN" class="language-bash line-numbers"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> tailwindcss</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div> <h2 id="create-your-style-file" style="position:relative;"><a href="#create-your-style-file" aria-label="create your style file permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Create your style file</h2> <p>At this stage you’re ready to create your style file. I will put it next to my <code class="language-text">main.js</code> for convinience.</p> <p><img src="./main-css.jpg" alt="Main style file"></p> <p>Don’t forget to import it inside your main.js file:</p> <div class="gatsby-code-button-container" data-toaster-id="74624732028545840000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`import &quot;./main.css&quot;; // ...`, `74624732028545840000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="js"><pre style="counter-reset: linenumber NaN" class="language-js line-numbers"><code class="language-js"><span class="token keyword">import</span> <span class="token string">"./main.css"</span><span class="token punctuation">;</span> <span class="token comment">// ...</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div> <p>And now is the time to import the <strong>Tailwincss</strong> base and components in our <code class="language-text">css</code> file:</p> <div class="gatsby-code-button-container" data-toaster-id="3017714544803751400" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`/* src/main.css */ @import &quot;tailwindcss/base&quot;; @import &quot;tailwindcss/components&quot;; @import &quot;tailwindcss/utilities&quot;;`, `3017714544803751400`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token comment">/* src/main.css */</span> <span class="token atrule"><span class="token rule">@import</span> <span class="token string">"tailwindcss/base"</span><span class="token punctuation">;</span></span> <span class="token atrule"><span class="token rule">@import</span> <span class="token string">"tailwindcss/components"</span><span class="token punctuation">;</span></span> <span class="token atrule"><span class="token rule">@import</span> <span class="token string">"tailwindcss/utilities"</span><span class="token punctuation">;</span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="postcss-configuration" style="position:relative;"><a href="#postcss-configuration" aria-label="postcss configuration permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Postcss configuration</h2> <p>Many articles tell you to create a <code class="language-text">postcss.config.js</code> or <code class="language-text">.postcssrc.js</code> and set your config there, but with the new version of Vue CLI this doesn’t get picked up. For this part we simply need to update our <code class="language-text">package.json</code> file:</p> <div class="gatsby-code-button-container" data-toaster-id="77856081723483770000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`&quot;postcss&quot;: { &quot;plugins&quot;: { &quot;tailwindcss&quot;: {}, &quot;autoprefixer&quot;: {} } }`, `77856081723483770000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="json"><pre style="counter-reset: linenumber NaN" class="language-json line-numbers"><code class="language-json"><span class="token property">"postcss"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"plugins"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"tailwindcss"</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"autoprefixer"</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div> <h2 id="import-tailwindcss-components" style="position:relative;"><a href="#import-tailwindcss-components" aria-label="import tailwindcss components permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Import Tailwindcss components</h2> <p>You’re ready to use the Tailwindcss components now. So open up your hello-world.vue file and paste this code from <a href="https://tailwindui.com/preview" target="_blank" rel="nofollow noopener noreferrer">their free gallery</a> in:</p> <div class="gatsby-code-button-container" data-toaster-id="96884515552698300000" data-toaster-class="gatsby-code-button-toaster" data-toaster-text-class="gatsby-code-button-toaster-text" data-toaster-text="Copied to clipboard ✅" data-toaster-duration="3500" onClick="copyToClipboard(`<!-- Tailwind UI components require Tailwind CSS v1.8 and the @tailwindcss/ui plugin. Read the documentation to get started: https://tailwindui.com/documentation --> <div class=&quot;relative bg-white overflow-hidden&quot;> <div class=&quot;max-w-screen-xl mx-auto&quot;> <div class=&quot;relative z-10 pb-8 bg-white sm:pb-16 md:pb-20 lg:max-w-2xl lg:w-full lg:pb-28 xl:pb-32&quot;> <svg class=&quot;hidden lg:block absolute right-0 inset-y-0 h-full w-48 text-white transform translate-x-1/2&quot; fill=&quot;currentColor&quot; viewBox=&quot;0 0 100 100&quot; preserveAspectRatio=&quot;none&quot;> <polygon points=&quot;50,0 100,0 50,100 0,100&quot; /> </svg> <div class=&quot;relative pt-6 px-4 sm:px-6 lg:px-8&quot;> <nav class=&quot;relative flex items-center justify-between sm:h-10 lg:justify-start&quot;> <div class=&quot;flex items-center flex-grow flex-shrink-0 lg:flex-grow-0&quot;> <div class=&quot;flex items-center justify-between w-full md:w-auto&quot;> <a href=&quot;#&quot; aria-label=&quot;Home&quot;> <img class=&quot;h-8 w-auto sm:h-10&quot; src=&quot;https://tailwindui.com/img/logos/workflow-mark-on-white.svg&quot; alt=&quot;Logo&quot;> </a> <div class=&quot;-mr-2 flex items-center md:hidden&quot;> <button type=&quot;button&quot; class=&quot;inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out&quot; id=&quot;main-menu&quot; aria-label=&quot;Main menu&quot; aria-haspopup=&quot;true&quot;> <svg class=&quot;h-6 w-6&quot; stroke=&quot;currentColor&quot; fill=&quot;none&quot; viewBox=&quot;0 0 24 24&quot;> <path stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;M4 6h16M4 12h16M4 18h16&quot; /> </svg> </button> </div> </div> </div> <div class=&quot;hidden md:block md:ml-10 md:pr-4&quot;> <a href=&quot;#&quot; class=&quot;font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out&quot;>Product</a> <a href=&quot;#&quot; class=&quot;ml-8 font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out&quot;>Features</a> <a href=&quot;#&quot; class=&quot;ml-8 font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out&quot;>Marketplace</a> <a href=&quot;#&quot; class=&quot;ml-8 font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out&quot;>Company</a> <a href=&quot;#&quot; class=&quot;ml-8 font-medium text-indigo-600 hover:text-indigo-900 transition duration-150 ease-in-out&quot;>Log in</a> </div> </nav> </div> <!-- Mobile menu, show/hide based on menu open state. Entering: &quot;duration-150 ease-out&quot; From: &quot;opacity-0 scale-95&quot; To: &quot;opacity-100 scale-100&quot; Leaving: &quot;duration-100 ease-in&quot; From: &quot;opacity-100 scale-100&quot; To: &quot;opacity-0 scale-95&quot; --> <div class=&quot;absolute top-0 inset-x-0 p-2 transition transform origin-top-right md:hidden&quot;> <div class=&quot;rounded-lg shadow-md&quot;> <div class=&quot;rounded-lg bg-white shadow-xs overflow-hidden&quot; role=&quot;menu&quot; aria-orientation=&quot;vertical&quot; aria-labelledby=&quot;main-menu&quot;> <div class=&quot;px-5 pt-4 flex items-center justify-between&quot;> <div> <img class=&quot;h-8 w-auto&quot; src=&quot;https://tailwindui.com/img/logos/workflow-mark-on-white.svg&quot; alt=&quot;&quot;> </div> <div class=&quot;-mr-2&quot;> <button type=&quot;button&quot; class=&quot;inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out&quot; aria-label=&quot;Close menu&quot;> <svg class=&quot;h-6 w-6&quot; stroke=&quot;currentColor&quot; fill=&quot;none&quot; viewBox=&quot;0 0 24 24&quot;> <path stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;M6 18L18 6M6 6l12 12&quot; /> </svg> </button> </div> </div> <div class=&quot;px-2 pt-2 pb-3&quot;> <a href=&quot;#&quot; class=&quot;block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-50 transition duration-150 ease-in-out&quot; role=&quot;menuitem&quot;>Product</a> <a href=&quot;#&quot; class=&quot;mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-50 transition duration-150 ease-in-out&quot; role=&quot;menuitem&quot;>Features</a> <a href=&quot;#&quot; class=&quot;mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-50 transition duration-150 ease-in-out&quot; role=&quot;menuitem&quot;>Marketplace</a> <a href=&quot;#&quot; class=&quot;mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-50 transition duration-150 ease-in-out&quot; role=&quot;menuitem&quot;>Company</a> </div> <div> <a href=&quot;#&quot; class=&quot;block w-full px-5 py-3 text-center font-medium text-indigo-600 bg-gray-50 hover:bg-gray-100 hover:text-indigo-700 focus:outline-none focus:bg-gray-100 focus:text-indigo-700 transition duration-150 ease-in-out&quot; role=&quot;menuitem&quot;> Log in </a> </div> </div> </div> </div> <main class=&quot;mt-10 mx-auto max-w-screen-xl px-4 sm:mt-12 sm:px-6 md:mt-16 lg:mt-20 lg:px-8 xl:mt-28&quot;> <div class=&quot;sm:text-center lg:text-left&quot;> <h2 class=&quot;text-4xl tracking-tight leading-10 font-extrabold text-gray-900 sm:text-5xl sm:leading-none md:text-6xl&quot;> Data to enrich your <br class=&quot;xl:hidden&quot;> <span class=&quot;text-indigo-600&quot;>online business</span> </h2> <p class=&quot;mt-3 text-base text-gray-500 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0&quot;> Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua. </p> <div class=&quot;mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-start&quot;> <div class=&quot;rounded-md shadow&quot;> <a href=&quot;#&quot; class=&quot;w-full flex items-center justify-center px-8 py-3 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10&quot;> Get started </a> </div> <div class=&quot;mt-3 sm:mt-0 sm:ml-3&quot;> <a href=&quot;#&quot; class=&quot;w-full flex items-center justify-center px-8 py-3 border border-transparent text-base leading-6 font-medium rounded-md text-indigo-700 bg-indigo-100 hover:text-indigo-600 hover:bg-indigo-50 focus:outline-none focus:shadow-outline-indigo focus:border-indigo-300 transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10&quot;> Live demo </a> </div> </div> </div> </main> </div> </div> <div class=&quot;lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2&quot;> <img class=&quot;h-56 w-full object-cover sm:h-72 md:h-96 lg:w-full lg:h-full&quot; src=&quot;https://images.unsplash.com/photo-1551434678-e076c223a692?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80&quot; alt=&quot;&quot;> </div> </div>`, `96884515552698300000`)" > <div class="gatsby-code-button" data-tooltip="" > Copy<svg class="gatsby-code-button-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"/></svg> </div> </div> <div class="gatsby-highlight" data-language="html"><pre style="counter-reset: linenumber NaN" class="language-html line-numbers"><code class="language-html"><span class="token comment">&lt;!-- Tailwind UI components require Tailwind CSS v1.8 and the @tailwindcss/ui plugin. Read the documentation to get started: https://tailwindui.com/documentation --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>relative bg-white overflow-hidden<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>max-w-screen-xl mx-auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>relative z-10 pb-8 bg-white sm:pb-16 md:pb-20 lg:max-w-2xl lg:w-full lg:pb-28 xl:pb-32<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hidden lg:block absolute right-0 inset-y-0 h-full w-48 text-white transform translate-x-1/2<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>currentColor<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 100 100<span class="token punctuation">"</span></span> <span class="token attr-name">preserveAspectRatio</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>none<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>polygon</span> <span class="token attr-name">points</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>50,0 100,0 50,100 0,100<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>relative pt-6 px-4 sm:px-6 lg:px-8<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>relative flex items-center justify-between sm:h-10 lg:justify-start<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>flex items-center flex-grow flex-shrink-0 lg:flex-grow-0<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>flex items-center justify-between w-full md:w-auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Home<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>h-8 w-auto sm:h-10<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://tailwindui.com/img/logos/workflow-mark-on-white.svg<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Logo<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>-mr-2 flex items-center md:hidden<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>main-menu<span class="token punctuation">"</span></span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Main menu<span class="token punctuation">"</span></span> <span class="token attr-name">aria-haspopup</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>h-6 w-6<span class="token punctuation">"</span></span> <span class="token attr-name">stroke</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>currentColor<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>none<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 24 24<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">stroke-linecap</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>round<span class="token punctuation">"</span></span> <span class="token attr-name">stroke-linejoin</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>round<span class="token punctuation">"</span></span> <span class="token attr-name">stroke-width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M4 6h16M4 12h16M4 18h16<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punct