DEV Community: Yaser Adel Mehraban
The latest articles on DEV Community by Yaser Adel Mehraban (@yashints).
https://dev.to/yashints
https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F151401%2F4259d1b6-8e5b-47f0-b16b-eaa71e02ec62.jpeg
DEV Community: Yaser Adel Mehraban
https://dev.to/yashints
en
-
I wrote a book π, and here is how
Yaser Adel Mehraban
Thu, 02 Dec 2021 09:50:29 +0000
https://dev.to/yashints/i-wrote-a-book-and-here-is-how-9ch
https://dev.to/yashints/i-wrote-a-book-and-here-is-how-9ch
<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="proxy.php?url=https://www.amazon.com/author/yas">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>
<h2>
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>
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>
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>
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>
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>
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>
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>
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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview">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>
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>
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>
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>
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>
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>
About my book
</h2>
<p><a href="proxy.php?url=https://www.amazon.com/gp/product/B09MFY582M/ref=dbs_a_def_rwt_bibl_vppi_i0">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><a href="proxy.php?url=https://www.amazon.com/gp/product/B09MFY582M/ref=dbs_a_def_rwt_bibl_vppi_i0"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--snMlC0Wo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gl5z8v2kav7at1qgrtfp.jpg" alt="Infrastructure as code with Azure Bicep" width="405" height="500"></a></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>
books
azure
bicep
devops
-
Crowbits - STEM toy for kids π
Yaser Adel Mehraban
Mon, 19 Jul 2021 04:13:04 +0000
https://dev.to/yashints/crowbits-stem-toy-for-kids-2ggc
https://dev.to/yashints/crowbits-stem-toy-for-kids-2ggc
<p>Last year I backed up the <a href="proxy.php?url=https://www.kickstarter.com/projects/elecrow/crowbits-electronic-blocks-for-stem-education-at-any-level">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="proxy.php?url=https://www.elecrow.com/crowbits-kit/kits.html">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>
<h2>
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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--n9xdjxSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1afzlc2s9zlmnqrjf3vt.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--n9xdjxSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1afzlc2s9zlmnqrjf3vt.jpg" alt="Crowbits kit"></a></p>
<h2>
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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--xmIY0hLN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73061x4rg9vq5cmnxl3d.gif" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--xmIY0hLN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73061x4rg9vq5cmnxl3d.gif" alt="Crowbits robot walking"></a></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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--wos8jucF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv1b8i6jflgtdzm9hqex.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--wos8jucF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv1b8i6jflgtdzm9hqex.png" alt="Crowbits letscode software"></a></p>
<h2>
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>
So
</h2>
<p>So don't wait, go order your desired kit and get your kids (or yourself) busy building cool stuff.</p>
crowbits
stem
kids
learning
-
Azure AD concepts; Part one - All about entities π
Yaser Adel Mehraban
Sun, 04 Jul 2021 22:30:46 +0000
https://dev.to/yashints/part-one-all-about-entities-2b2n
https://dev.to/yashints/part-one-all-about-entities-2b2n
<p><a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals#application-registration">Application registration, service principal</a>, <a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview">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>
<h2>
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="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/authentication/overview-authentication">Authentication</a></li>
<li><a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-vs-authorization">Authorization</a></li>
<li><a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/authentication/concept-mfa-howitworks">Multi-Factor authentication</a></li>
<li><a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/what-is-single-sign-on">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>
OpenID Connect
</h2>
<p><a href="proxy.php?url=https://openid.net/connect/">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="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/auth-oidc">our documentation here</a>.</p>
<h2>
OAuth 2.0
</h2>
<p>While OIDC deals with authentication, it relies on <a href="proxy.php?url=https://oauth.net/2/">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="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/auth-oauth2">our documentation</a>.</p>
<p>Now that we know enough about these concepts, let's dip our toes into the ocean.</p>
<h2>
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>
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>
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>
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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--0zcq3uTF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1d728peyhxhyhmntr8ih.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--0zcq3uTF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1d728peyhxhyhmntr8ih.png" alt="Azure Active Directory Security Principal types"></a></p>
<h2>
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="proxy.php?url=https://docs.microsoft.com/en-us/azure/active-directory/saas-apps/">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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--quIoidKY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lf6nyr44xhbqt7ykao6.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--quIoidKY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lf6nyr44xhbqt7ykao6.png" alt="Enterprise Applications in Azure Active Directory"></a></p>
<h2>
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>
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="proxy.php?url=https://azure.microsoft.com/en-au/services/app-service/">Azure App Services</a> (using two separate app registration)</li>
<li>A SPA consuming an <a href="proxy.php?url=https://azure.microsoft.com/en-au/services/functions/">Azure Function</a> (using app registration and managed identity)</li>
<li>A SPA consuming an API hosted on <a href="proxy.php?url=https://azure.microsoft.com/en-au/services/api-management/">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>
azure
showdev
aad
oauth
-
From ARM to Bicep πͺπ½
Yaser Adel Mehraban
Mon, 10 May 2021 02:13:56 +0000
https://dev.to/azure/from-arm-to-bicep-251c
https://dev.to/azure/from-arm-to-bicep-251c
<p>If you have deployed a resource in <a href="proxy.php?url=https://azure.microsoft.com/">Microsoft Azure</a> as part of your CI/CD pipeline you have probably worked with <a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview">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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/bicep-overview">Bicep</a> which is designed to overcome these issues and help you with your infrastructure as code setup.</p>
<h2>
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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview">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>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"</span><span class="p">,</span><span class="w">
</span><span class="nl">"contentVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"storageAccountType"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"secureString"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"variables"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"diagStorageAccountName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[concat('diags', uniqueString(resourceGroup().id))]"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"resources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Microsoft.Storage/storageAccounts"</span><span class="p">,</span><span class="w">
</span><span class="nl">"apiVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2019-06-01"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[variables('diagStorageAccountName')]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"location"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[resourceGroup().location]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"sku"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[parameters('storageAccountType')]"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Storage"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>Using Bicep it will be simplified to:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="p">@</span><span class="nd">secure</span><span class="p">()</span>
<span class="nx">param</span> <span class="nx">storageAccountType</span> <span class="nx">string</span>
<span class="nx">param</span> <span class="nx">location</span> <span class="nx">string</span> <span class="o">=</span> <span class="nx">resourceGroup</span><span class="p">().</span><span class="nx">location</span>
<span class="kd">var</span> <span class="nx">diagStorageAccountName</span> <span class="o">=</span> <span class="nx">concat</span><span class="p">(</span><span class="dl">'</span><span class="s1">diags</span><span class="dl">'</span><span class="p">,</span> <span class="nx">uniqueString</span><span class="p">(</span><span class="nx">resourceGroup</span><span class="p">().</span><span class="nx">id</span><span class="p">))</span>
<span class="nx">resource</span> <span class="nx">diagsAccount</span> <span class="dl">'</span><span class="s1">Microsoft.Storage/storageAccounts@2019-06-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">diagStorageAccountName</span>
<span class="na">location</span><span class="p">:</span> <span class="nx">location</span>
<span class="na">sku</span><span class="p">:</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">storageAccountType</span>
<span class="p">}</span>
<span class="nl">kind</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Storage</span><span class="dl">'</span>
<span class="p">}</span>
</code></pre>
</div>
<p>You can see how much time and space you could be saving if you were to use Bicep.</p>
<h2>
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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/bicep-modules">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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-deploy-what-if">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>
Syntax
</h2>
<p>Every Bicep resource will have the below syntax:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">resource</span> <span class="o"><</span><span class="nx">symbolic</span><span class="o">-</span><span class="nx">name</span><span class="o">></span> <span class="dl">'</span><span class="s1"><resource-type>@<api-version>` = {
//properties
name: </span><span class="dl">'</span><span class="nx">bicepstorage2063</span><span class="dl">'</span><span class="s1">
location: </span><span class="dl">'</span><span class="nx">northcentralus</span><span class="dl">'</span><span class="s1">
properties: {
//...sub properties
}
}
</span></code></pre>
</div>
<p>Where:</p>
<ul>
<li>
<code>resource</code>: is a reserved keyword.</li>
<li>
<code>symbolic name</code>: is an identifier within the Bicep file which can be used to reference this resource elsewhere.</li>
<li>
<code>resource-type</code>: is the type of the resource you're defining, e.g. <code>Microsoft.Storage</code>.</li>
<li>
<code>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>properties</code>: these are the resource specific properties. For example every resource has a <code>name</code> and <code>location</code>. In addition some have sub properties which you can pass on.</li>
</ul>
<h2>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">param</span> <span class="o"><</span><span class="nx">parameter</span><span class="o">-</span><span class="nx">name</span><span class="o">></span> <span class="o"><</span><span class="nx">parameter</span><span class="o">-</span><span class="nx">type</span><span class="o">></span> <span class="o">=</span> <span class="o"><</span><span class="nx">parameter</span><span class="o">-</span><span class="nx">value</span><span class="o">></span>
</code></pre>
</div>
<p>Where:</p>
<ul>
<li>
<code>param</code>: is a reserved keyword.</li>
<li>
<code>parameter-name</code> is the name of the parameter.</li>
<li>
<code>parameter-type</code>: is the type of the parameter such as <code>string</code>, <code>object</code>, etc.</li>
<li>
<code>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.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="p">@</span><span class="nd">minLength</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="p">@</span><span class="nd">maxLength</span><span class="p">(</span><span class="mi">24</span><span class="p">)</span>
<span class="nx">param</span> <span class="nx">storageName</span> <span class="nx">string</span>
</code></pre>
</div>
<p>In this example you're limiting the <code>storageName</code> parameter's value length to be between 3 and 24 characters. Or:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="p">@</span><span class="nd">allowed</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">Standard_LRS</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">Standard_GRS</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">Standard_RAGRS</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">Standard_ZRS</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">Premium_LRS</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">Premium_ZRS</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">Standard_GZRS</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">Standard_RAGZRS</span><span class="dl">'</span>
<span class="p">])</span>
<span class="nx">param</span> <span class="nx">storageRedundancy</span> <span class="nx">string</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Standard_LRS</span><span class="dl">'</span>
</code></pre>
</div>
<p>In this example you're specifying the allowed values for the <code>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>.parameters.json</code>. In Bicep you need to use the same JSON file to pass the parameters in:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"</span><span class="p">,</span><span class="w">
</span><span class="nl">"contentVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"storageName"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"myuniquestoragename"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"storageRedundancy"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Standard_GZRS"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<h2>
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>var</code> keyword:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">var</span> <span class="o"><</span><span class="nx">variable</span><span class="o">-</span><span class="nx">name</span><span class="o">></span> <span class="o">=</span> <span class="o"><</span><span class="nx">value</span><span class="o">></span>
</code></pre>
</div>
<p>Where <code>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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">storageAccName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">sa${uniqueString(resourceGroup().id)}</span><span class="dl">'</span>
<span class="nx">resource</span> <span class="nx">stg</span> <span class="dl">'</span><span class="s1">Microsoft.Storage/storageAccounts@2019-06-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">storageAccountName</span>
<span class="c1">//...</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Since we need a unique name for our storage account the <code>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>
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>
functions
</h3>
<p>The good thing is that <strong>ANY</strong> valid <a href="proxy.php?url=https://docs.microsoft.com/azure/azure-resource-manager/templates/template-functions">ARM template function</a> is also a valid Bicep function.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">param</span> <span class="nx">currentTime</span> <span class="nx">string</span> <span class="o">=</span> <span class="nx">utcNow</span><span class="p">()</span>
<span class="kd">var</span> <span class="nx">location</span> <span class="o">=</span> <span class="nx">resourceGroup</span><span class="p">().</span><span class="nx">location</span>
<span class="kd">var</span> <span class="nx">makeCapital</span> <span class="o">=</span> <span class="nx">toUpper</span><span class="p">(</span><span class="dl">'</span><span class="s1">all lowercase</span><span class="dl">'</span><span class="p">)</span>
</code></pre>
</div>
<h3>
Ternary operator
</h3>
<p>To use conditions in your deployments you would use the <code>if</code> function in ARM templates, however, that's not supported in Bicep. Instead, you can leverage the <strong>ternary operator</strong>:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">param</span> <span class="nx">globalRedundancy</span> <span class="nx">bool</span> <span class="o">=</span> <span class="kc">true</span>
<span class="nx">resource</span> <span class="nx">stg</span> <span class="dl">'</span><span class="s1">Microsoft.Storage/storageAccounts@2019-06-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">storageAccountName</span>
<span class="na">location</span><span class="p">:</span> <span class="nx">location</span>
<span class="na">kind</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Storage</span><span class="dl">'</span>
<span class="na">sku</span><span class="p">:</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">globalRedundancy</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">Standard_GRS</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">Standard_LRS</span><span class="dl">'</span> <span class="c1">// if true --> GRS, else --> LRS</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<h2>
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>output</code> keyword.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">resource</span> <span class="nx">stg</span> <span class="dl">'</span><span class="s1">Microsoft.Storage/storageAccounts@2019-06-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">{</span>
<span class="c1">//...</span>
<span class="p">}</span>
<span class="nx">output</span> <span class="nx">storageId</span> <span class="nx">string</span> <span class="o">=</span> <span class="nx">stg</span><span class="p">.</span><span class="nx">id</span>
</code></pre>
</div>
<p>This will return the storage id out to be used later.</p>
<h2>
Loops
</h2>
<p>In ARM templates if you wanted to deploy a resource multiple times you could leverage the <code>copy</code> operator to add a resource <code>n</code> times based on the loop count. In Bicep you have the <code>for</code> operator at your disposal:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">resource</span> <span class="nx">foo</span> <span class="dl">'</span><span class="s1">my.provider/type@2021-03-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="o"><</span><span class="nx">ITERATOR_NAME</span><span class="o">></span> <span class="k">in</span> <span class="o"><</span><span class="nx">ARRAY</span><span class="o">></span> <span class="o">=</span> <span class="p">{...}]</span>
</code></pre>
</div>
<p>Where <code>ITERATOR_NAME</code> is a new symbol that's only available inside your resource declaration.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">param</span> <span class="nx">containerNames</span> <span class="nx">array</span> <span class="o">=</span> <span class="p">[</span>
<span class="dl">'</span><span class="s1">images</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">videos</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">pdf</span><span class="dl">'</span>
<span class="p">]</span>
<span class="nx">resource</span> <span class="nx">blob</span> <span class="dl">'</span><span class="s1">Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="nx">name</span> <span class="k">in</span> <span class="nx">containerNames</span><span class="p">:</span> <span class="p">{</span>
<span class="nl">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">${stg.name}/default/${name}</span><span class="dl">'</span>
<span class="c1">//...</span>
<span class="p">}]</span>
</code></pre>
</div>
<p>This snippet creates three containers within the storage account in a loop.</p>
<h2>
Existing keyword
</h2>
<p>If you want to deploy a resource which is depending on an existing resource you can leverage the <code>existing</code> keyword.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">resource</span> <span class="nx">stg</span> <span class="dl">'</span><span class="s1">Microsoft.Storage/storageAccounts@2019-06-01</span><span class="dl">'</span> <span class="nx">existing</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">storageAccountName</span>
<span class="p">}</span>
</code></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>
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>modules</code>. You can define a resource in a module and reuse that module in other Bicep files.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">.</span>
βββ main.bicep
βββ stg.bicep
</code></pre>
</div>
<p>In our <code>stg</code> file you will define the resource, its parameters, variables, outputs, etc:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">//stg.bicep</span>
<span class="nx">param</span> <span class="nx">storageAccountName</span>
<span class="kd">var</span> <span class="nx">storageSku</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Standard_LRS</span><span class="dl">'</span>
<span class="nx">resource</span> <span class="nx">stg</span> <span class="dl">'</span><span class="s1">Microsoft.Storage/storageAccounts@2019-06-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">storageAccountName</span>
<span class="na">location</span><span class="p">:</span> <span class="nx">resourceGroup</span><span class="p">().</span><span class="nx">location</span>
<span class="na">kind</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Storage</span><span class="dl">'</span>
<span class="na">sku</span><span class="p">:</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">storageSku</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>And in the <code>main</code> file you will reuse the storage account as a module using the <code>module</code> keyword:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">//main.bicep</span>
<span class="nx">module</span> <span class="nx">stg</span> <span class="dl">'</span><span class="s1">./storage.bicep</span><span class="dl">'</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">storageDeploy</span><span class="dl">'</span>
<span class="na">params</span><span class="p">:</span> <span class="p">{</span>
<span class="na">storageAccountName</span><span class="p">:</span> <span class="dl">'</span><span class="s1"><YOURUNIQUESTORAGENAME></span><span class="dl">'</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">output</span> <span class="nx">storageName</span> <span class="nx">array</span> <span class="o">=</span> <span class="nx">stg</span><span class="p">.</span><span class="nx">outputs</span><span class="p">.</span><span class="nx">containerProps</span>
</code></pre>
</div>
<p>You only need to pass the required properties which in case of our storage account is the name.</p>
<h2>
The <code>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>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>int</code>, but in fact they are <code>number</code> since you can pass non-integer values such as <code>0.5</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">resource</span> <span class="nx">wpAci</span> <span class="dl">'</span><span class="s1">microsoft.containerInstance/containerGroups@2019-12-01</span><span class="dl">'</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">wordpress-containerinstance</span><span class="dl">'</span>
<span class="na">location</span><span class="p">:</span> <span class="nx">location</span>
<span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
<span class="na">containers</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">wordpress</span><span class="dl">'</span>
<span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
<span class="p">...</span>
<span class="na">resources</span><span class="p">:</span> <span class="p">{</span>
<span class="na">requests</span><span class="p">:</span> <span class="p">{</span>
<span class="na">cpu</span><span class="p">:</span> <span class="nx">any</span><span class="p">(</span><span class="dl">'</span><span class="s1">0.5</span><span class="dl">'</span><span class="p">)</span>
<span class="na">memoryInGB</span><span class="p">:</span> <span class="nx">any</span><span class="p">(</span><span class="dl">'</span><span class="s1">0.7</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>By using <code>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>
Tooling
</h2>
<p>In terms of tooling the support is the same if not better than the ARM templates.</p>
<h3>
VS Code extension
</h3>
<p>VS Code comes with <a href="proxy.php?url=https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep">an official extension for Bicep</a>. This extension gives you validation, intellisense, dot property access, snippets etc.</p>
<h3>
CI/CD
</h3>
<p>If you're using <a href="proxy.php?url=https://github.com/features/actions">GitHub Actions</a> for your CI/CD pipeline, there is already a <a href="proxy.php?url=https://github.com/marketplace/actions/bicep-build">Bicep action</a> created by our developer advocate <a href="proxy.php?url=https://github.com/justinyoo">Justin Yoo</a> which you can use to build you bicep file and deploy it to Azure.</p>
<h3>
CLI
</h3>
<p>Bicep comes with a CLI that you can install locally on <a href="proxy.php?url=https://github.com/Azure/bicep/blob/main/docs/installing.md">Windows, MacOS, and Linux</a>. That gives you the ability to build and deploy your Bicep files with <a href="proxy.php?url=https://docs.microsoft.com/en-us/cli/azure">Azure CLI</a>.</p>
<h2>
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>
showdev
azure
arm
bicep
-
Exploring Azure Function Triggers and Bindings β‘
Yaser Adel Mehraban
Mon, 29 Mar 2021 00:13:49 +0000
https://dev.to/yashints/exploring-azure-function-triggers-and-bindings-l7n
https://dev.to/yashints/exploring-azure-function-triggers-and-bindings-l7n
<p><a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/">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>
<h2>
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="proxy.php?url=https://code.visualstudio.com/">Visual Studio Code</a>, you need to install the <a href="proxy.php?url=https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions">Azure Functions Extensions</a>. For the command line approach, you'll need the <a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#v2">Azure Functions Core Tools</a> and either <a href="proxy.php?url=https://docs.microsoft.com/en-us/cli/azure/install-azure-cli">Azure CLI</a> or <a href="proxy.php?url=https://docs.microsoft.com/en-us/powershell/azure/install-az-ps">Azure PowerShell</a> to create Azure resources. You can also use the <a href="proxy.php?url=https://azure.microsoft.com/downloads/">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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-your-first-function-visual-studio">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="proxy.php?url=https://docs.microsoft.com/en-us/windows/terminal/">Windows Terminal</a>) and initialize a new function using the init command:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>func init myLocalFunctionProj <span class="nt">--dotnet</span>
<span class="nb">cd </span>myLocalFunctionProj
</code></pre>
</div>
<p>You can use other languages too. For a list of supported languages please <a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/supported-languages">visit the our documentation</a>.</p>
<p>Now that you have a project you can create one or multiple functions in it:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>func new <span class="nt">--name</span> HttpExample <span class="nt">--template</span> <span class="s2">"HTTP trigger"</span> <span class="nt">--authlevel</span> <span class="s2">"function"</span>
</code></pre>
</div>
<p>This will create an HTTP triggered function for you which you can run using:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>func start
</code></pre>
</div>
<p>Now you can send request to your function, so open a browser and visit <code>http://localhost:7071/api/HttpExample?name=Yas</code>, the function should return the result and the browser should show you a <code>Hello Yas</code> message. You're now ready to explore triggers and binding.</p>
<h2>
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>
<div class="table-wrapper-paragraph"><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></div>
<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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?tabs=csharp">Azure Functions documentation site</a>.</p>
<h3>
Timer trigger
</h3>
<p>Timer triggers allow you to run the function on a schedule. All you need is a <code>TimerTrigger</code> attribute with its configuration:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"TimerTriggerCSharp"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">Run</span><span class="p">([</span><span class="nf">TimerTrigger</span><span class="p">(</span><span class="s">"0 */5 * * * *"</span><span class="p">)]</span><span class="n">TimerInfo</span> <span class="n">myTimer</span><span class="p">,</span> <span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">myTimer</span><span class="p">.</span><span class="n">IsPastDue</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">log</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">"Timer is running late!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">log</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">$"C# Timer trigger function executed at: </span><span class="p">{</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>
<p>This function will run based on the <a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=csharp#ncrontab-expressions">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>
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.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"BlobTriggerCSharp"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">Run</span><span class="p">([</span><span class="nf">BlobTrigger</span><span class="p">(</span><span class="s">"container-name/{name}"</span><span class="p">)]</span> <span class="n">Stream</span> <span class="n">myBlob</span><span class="p">,</span> <span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">log</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">$"C# Blob trigger function Processed blob\n Name:</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s"> \n Size: </span><span class="p">{</span><span class="n">myBlob</span><span class="p">.</span><span class="n">Length</span><span class="p">}</span><span class="s"> Bytes"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>
<blockquote>
<p>π‘ You will need the <code>Microsoft.Azure.WebJobs.Extensions</code> package to use these attributes.</p>
</blockquote>
<h3>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"CosmosTrigger"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">Run</span><span class="p">([</span><span class="nf">CosmosDBTrigger</span><span class="p">(</span>
<span class="n">databaseName</span><span class="p">:</span> <span class="s">"ToDoItems"</span><span class="p">,</span>
<span class="n">collectionName</span><span class="p">:</span> <span class="s">"Items"</span><span class="p">,</span>
<span class="n">ConnectionStringSetting</span> <span class="p">=</span> <span class="s">"CosmosDBConnection"</span><span class="p">)]</span><span class="n">IReadOnlyList</span><span class="p"><</span><span class="n">Document</span><span class="p">></span> <span class="n">documents</span><span class="p">,</span>
<span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">documents</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&&</span> <span class="n">documents</span><span class="p">.</span><span class="n">Count</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">log</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">$"Documents modified: </span><span class="p">{</span><span class="n">documents</span><span class="p">.</span><span class="n">Count</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="n">log</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">$"First document Id: </span><span class="p">{</span><span class="n">documents</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="n">Id</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>All you need is the connection string in your <code>local.settings.json</code> in the connection string section:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"IsEncrypted"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"Values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"AzureWebJobsStorage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<storage-connection-string>"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"ConnectionStrings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"CosmosDBConnection"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<cosmosdb-connection-string>"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-trigger?tabs=csharp">reference documentation section</a>.</p>
<h2>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"BlobInput"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">Run</span><span class="p">(</span>
<span class="p">[</span><span class="nf">QueueTrigger</span><span class="p">(</span><span class="s">"queue-name"</span><span class="p">)]</span> <span class="kt">string</span> <span class="n">myQueueItem</span><span class="p">,</span>
<span class="p">[</span><span class="nf">Blob</span><span class="p">(</span><span class="s">"container/{queueTrigger}"</span><span class="p">,</span> <span class="n">FileAccess</span><span class="p">.</span><span class="n">Read</span><span class="p">)]</span> <span class="n">Stream</span> <span class="n">myBlob</span><span class="p">,</span>
<span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">log</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">$"BlobInput processed blob\n Name:</span><span class="p">{</span><span class="n">myQueueItem</span><span class="p">}</span><span class="s"> \n Size: </span><span class="p">{</span><span class="n">myBlob</span><span class="p">.</span><span class="n">Length</span><span class="p">}</span><span class="s"> bytes"</span><span class="p">);</span>
<span class="p">}</span>
</code></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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"CreateThumbnail"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">Run</span><span class="p">(</span>
<span class="p">[</span><span class="nf">BlobTrigger</span><span class="p">(</span><span class="s">"images/{name}"</span><span class="p">)]</span> <span class="n">Stream</span> <span class="n">image</span><span class="p">,</span>
<span class="p">[</span><span class="nf">Blob</span><span class="p">(</span><span class="s">"thumbnails/{name}"</span><span class="p">,</span> <span class="n">FileAccess</span><span class="p">.</span><span class="n">Write</span><span class="p">)]</span> <span class="n">Stream</span> <span class="n">thumbnail</span><span class="p">,</span>
<span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IImageFormat</span> <span class="n">format</span><span class="p">;</span>
<span class="k">using</span> <span class="p">(</span><span class="n">Image</span><span class="p"><</span><span class="n">Rgba32</span><span class="p">></span> <span class="n">input</span> <span class="p">=</span> <span class="n">Image</span><span class="p">.</span><span class="n">Load</span><span class="p"><</span><span class="n">Rgba32</span><span class="p">>(</span><span class="n">image</span><span class="p">,</span> <span class="k">out</span> <span class="n">format</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">input</span><span class="p">.</span><span class="nf">Mutate</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="nf">Resize</span><span class="p">(</span><span class="m">320</span><span class="p">,</span> <span class="m">200</span><span class="p">));</span>
<span class="n">input</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="n">format</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></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>{rand-guid}</code> to generate a unique identifier or <code>{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>{name}</code> for your output binding. To generate the name of the output blob at runtime, you need to use imperative bindings.</p>
<h3>
IBinder
</h3>
<p>Instead of using the <code>[Blob]</code> attribute, you could use an instance of <code>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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.IO</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Azure.WebJobs</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.Logging</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">SixLabors.ImageSharp</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">SixLabors.ImageSharp.Processing</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">SixLabors.ImageSharp.Formats</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading.Tasks</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">SixLabors.ImageSharp.PixelFormats</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">functions</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">createThumbnail</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"createThumbnail"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Run</span><span class="p">(</span>
<span class="p">[</span><span class="nf">BlobTrigger</span><span class="p">(</span><span class="s">"images/original-{blobName}"</span><span class="p">,</span> <span class="n">Connection</span> <span class="p">=</span> <span class="s">"AzureWebJobsStorage"</span><span class="p">)]</span> <span class="n">Stream</span> <span class="n">image</span><span class="p">,</span>
<span class="kt">string</span> <span class="n">blobName</span><span class="p">,</span>
<span class="n">IBinder</span> <span class="n">binder</span><span class="p">,</span>
<span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IImageFormat</span> <span class="n">format</span><span class="p">;</span>
<span class="k">using</span> <span class="p">(</span><span class="n">Image</span><span class="p"><</span><span class="n">Rgba32</span><span class="p">></span> <span class="n">input</span> <span class="p">=</span> <span class="n">Image</span><span class="p">.</span><span class="n">Load</span><span class="p"><</span><span class="n">Rgba32</span><span class="p">>(</span><span class="n">image</span><span class="p">,</span> <span class="k">out</span> <span class="n">format</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">input</span><span class="p">.</span><span class="nf">Mutate</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="nf">Resize</span><span class="p">(</span><span class="m">320</span><span class="p">,</span> <span class="m">200</span><span class="p">));</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">writer</span> <span class="p">=</span> <span class="k">await</span> <span class="n">binder</span><span class="p">.</span><span class="n">BindAsync</span><span class="p"><</span><span class="n">Stream</span><span class="p">>(</span>
<span class="k">new</span> <span class="nf">BlobAttribute</span><span class="p">(</span><span class="s">$"images/thumbnail-</span><span class="p">{</span><span class="n">blobName</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="n">FileAccess</span><span class="p">.</span><span class="n">Write</span><span class="p">)))</span>
<span class="p">{</span>
<span class="n">input</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="n">format</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></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>
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="proxy.php?url=https://docs.microsoft.com/en-us/azure/azure-functions/">you can find out about them here</a>. All you need to do is to select the <code>Reference > Triggers and bindings</code> from the left hand side.</p>
<h2>
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>
showdev
azure
azurefunctions
serverless
-
Checkout my new PluralSight course on securing a Vue.js app π
Yaser Adel Mehraban
Thu, 11 Feb 2021 20:53:53 +0000
https://dev.to/yashints/checkout-my-new-pluralsight-course-on-securing-a-vue-js-app-6d3
https://dev.to/yashints/checkout-my-new-pluralsight-course-on-securing-a-vue-js-app-6d3
<p>Hey friends,</p>
<p>Iβm delighted to let you know that my latest course on PluralSight just got published ππ₯³π₯³π₯³π€©.</p>
<p>Iβve covered many topics including:</p>
<ul>
<li>What is authentication and authorisation and whatβs the difference between them.</li>
<li>Modern concepts like OAuth 2.0 and OpenID Connect</li>
<li>Setting up login and sign up flows</li>
<li>Manage user information using state management </li>
<li>How to create route guards and centrally manage access to your pages</li>
<li>How to secure your backend APIs</li>
<li>How to integrate with Microsoft Azure Active Directory</li>
<li>How to integrate with Auth0</li>
<li>How to integrate with Okta</li>
<li>How to write component tests with Vue test utils and Jest</li>
<li>How to write end to end tests with Playwright</li>
</ul>
<p>So go check it out and hope you enjoy. If you liked it Iβd appreciate if you could spread the word so that others get to benefit too πͺπΌ.</p>
<p><a href="proxy.php?url=http://www.pluralsight.com/courses/vue-authentication-authorization">Vue Authentication and Authorization by Yas</a></p>
<p>Have a great day/evening/night wherever you are.</p>
<p>Ta,<br>
Yas</p>
webdev
showdev
vue
authentication
-
How to end-to-end test your Vue.js apps with Playwright π§ͺ
Yaser Adel Mehraban
Tue, 09 Feb 2021 04:33:10 +0000
https://dev.to/yashints/how-to-end-to-end-test-your-vue-js-apps-with-playwright-268f
https://dev.to/yashints/how-to-end-to-end-test-your-vue-js-apps-with-playwright-268f
<p><a href="proxy.php?url=https://playwright.dev/">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="proxy.php?url=https://vuejs.org/">Vue.js</a> application which is using <a href="proxy.php?url=https://auth0.com/">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>
<h2>
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>
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>
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>
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>
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):<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">createAuth0Client</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@auth0/auth0-spa-js</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">client</span><span class="p">;</span>
<span class="c1">///all the other methods and definitions</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">setupAuth</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">options</span><span class="p">,</span> <span class="nx">callbackRedirect</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">client</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">createAuth0Client</span><span class="p">({</span>
<span class="p">...</span><span class="nx">options</span><span class="p">,</span>
<span class="p">});</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">code=</span><span class="dl">'</span><span class="p">)</span>
<span class="o">&&</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">state=</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">appState</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">handleRedirectCallback</span><span class="p">();</span>
<span class="nx">callbackRedirect</span><span class="p">(</span><span class="nx">appState</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">//...</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">install</span><span class="p">:</span> <span class="nx">app</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">config</span><span class="p">.</span><span class="nx">globalProperties</span><span class="p">.</span><span class="nx">$auth</span> <span class="o">=</span> <span class="nx">authPlugin</span><span class="p">;</span>
<span class="p">},</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre>
</div>
<p>We also implement a route guard in the same file:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">computed</span><span class="p">,</span> <span class="nx">watchEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vue</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">authPlugin</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">isAuthenticated</span><span class="p">:</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">isAuthenticated</span><span class="p">),</span>
<span class="na">loading</span><span class="p">:</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">loading</span><span class="p">),</span>
<span class="na">user</span><span class="p">:</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">user</span><span class="p">),</span>
<span class="na">popupOpen</span><span class="p">:</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">popupOpen</span><span class="p">),</span>
<span class="na">claims</span><span class="p">:</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">claims</span><span class="p">),</span>
<span class="nx">getIdTokenClaims</span><span class="p">,</span>
<span class="nx">getTokenSilently</span><span class="p">,</span>
<span class="nx">getTokenWithPopup</span><span class="p">,</span>
<span class="nx">handleRedirectCallback</span><span class="p">,</span>
<span class="nx">loginWithRedirect</span><span class="p">,</span>
<span class="nx">loginWithPopup</span><span class="p">,</span>
<span class="nx">logout</span><span class="p">,</span>
<span class="nx">getUser</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">routeGuard</span> <span class="o">=</span> <span class="p">(</span><span class="nx">to</span><span class="p">,</span> <span class="k">from</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">isAuthenticated</span><span class="p">,</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">claims</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">authPlugin</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">verify</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isAuthenticated</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">next</span><span class="p">({</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/login</span><span class="dl">'</span><span class="p">,</span> <span class="na">query</span><span class="p">:</span> <span class="p">{</span> <span class="na">returnUrl</span><span class="p">:</span> <span class="nx">to</span><span class="p">.</span><span class="nx">path</span> <span class="p">}</span> <span class="p">});</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">to</span><span class="p">?.</span><span class="nx">meta</span><span class="p">?.</span><span class="nx">authorize</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">roles</span> <span class="o">=</span> <span class="nx">claims</span><span class="p">.</span><span class="nx">value</span><span class="p">[</span><span class="dl">'</span><span class="s1">http://schemas.microsoft.com/ws/2008/06/identity/claims/role</span><span class="dl">'</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">roles</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">r</span> <span class="o">=></span> <span class="nx">r</span> <span class="o">===</span> <span class="nx">to</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">authorize</span><span class="p">.</span><span class="nx">role</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">next</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">next</span><span class="p">(</span><span class="dl">'</span><span class="s1">/unauthorized</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">loading</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">verify</span><span class="p">();</span>
<span class="p">}</span>
<span class="nx">watchEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="kc">false</span> <span class="o">&&</span> <span class="nx">claims</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">verify</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">};</span>
</code></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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">createApp</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vue</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">router</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./router</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">setupAuth</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@/auth/auth-plugin</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">authConfig</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">domain</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">VUE_APP_DOMAIN</span><span class="p">,</span>
<span class="na">client_id</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">VUE_APP_CLIENTID</span><span class="p">,</span>
<span class="na">redirect_uri</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">VUE_APP_REDIRECT_URL</span><span class="p">,</span>
<span class="na">audience</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">VUE_APP_AUDIENCE</span><span class="p">,</span>
<span class="na">advancedOptions</span><span class="p">:</span> <span class="p">{</span>
<span class="na">defaultScope</span><span class="p">:</span> <span class="dl">'</span><span class="s1">openid profile email crud:users</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">};</span>
<span class="kd">function</span> <span class="nx">callbackRedirect</span><span class="p">(</span><span class="nx">appState</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">appState</span> <span class="o">&&</span> <span class="nx">appState</span><span class="p">.</span><span class="nx">targetUrl</span> <span class="p">?</span> <span class="nx">appState</span><span class="p">.</span><span class="nx">targetUrl</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">createApp</span><span class="p">(</span><span class="nx">App</span><span class="p">)</span>
<span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">router</span><span class="p">);</span>
<span class="nx">setupAuth</span><span class="p">(</span><span class="nx">authConfig</span><span class="p">,</span> <span class="nx">callbackRedirect</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">auth</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">auth</span><span class="p">).</span><span class="nx">mount</span><span class="p">(</span><span class="dl">'</span><span class="s1">#app</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></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>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>.use</code> and use our plugin instance.</p>
<h2>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><div</span> <span class="na">v-if=</span><span class="s">"!user"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"signup"</span> <span class="err">@</span><span class="na">click.prevent=</span><span class="s">"login"</span><span class="nt">></span>
You need to sign in first!
<span class="nt"></a></span>
<span class="nt"></div></span>
</code></pre>
</div>
<p>And in our component:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">/// code removed for brevity</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="na">methods</span><span class="p">:</span> <span class="p">{</span>
<span class="na">login</span><span class="p">:</span> <span class="k">async</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">$auth</span><span class="p">.</span><span class="nx">loginWithPopup</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">$auth</span><span class="p">.</span><span class="nx">getUser</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">accessToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">$auth</span><span class="p">.</span><span class="nx">getTokenSilently</span><span class="p">();</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$store</span><span class="p">.</span><span class="nx">commit</span><span class="p">(</span><span class="dl">'</span><span class="s1">SET_USER</span><span class="dl">'</span><span class="p">,</span> <span class="nx">user</span><span class="p">);</span>
<span class="c1">//...</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">//...</span>
<span class="p">}</span>
</code></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>
Router config
</h2>
<p>And the last thing we would have here would be the routing configuration:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">createWebHistory</span><span class="p">,</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vue-router</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">routeGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@/auth/auth-plugin</span><span class="dl">'</span><span class="p">;</span>
<span class="c1">//other imports</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">routes</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span>
<span class="na">component</span><span class="p">:</span> <span class="nx">Home</span><span class="p">,</span>
<span class="p">},</span>
<span class="c1">//...other routes</span>
<span class="p">{</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/login</span><span class="dl">'</span><span class="p">,</span>
<span class="na">component</span><span class="p">:</span> <span class="nx">Login</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/admin</span><span class="dl">'</span><span class="p">,</span>
<span class="na">component</span><span class="p">:</span> <span class="nx">Admin</span><span class="p">,</span>
<span class="na">beforeEnter</span><span class="p">:</span> <span class="nx">routeGuard</span><span class="p">,</span>
<span class="na">meta</span><span class="p">:</span> <span class="p">{</span>
<span class="na">authorize</span><span class="p">:</span> <span class="p">{</span>
<span class="na">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Admin</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/unauthorized</span><span class="dl">'</span><span class="p">,</span>
<span class="na">component</span><span class="p">:</span> <span class="nx">UnAuthorized</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">];</span>
<span class="kd">const</span> <span class="nx">router</span> <span class="o">=</span> <span class="nx">createRouter</span><span class="p">({</span>
<span class="na">history</span><span class="p">:</span> <span class="nx">createWebHistory</span><span class="p">(),</span>
<span class="nx">routes</span><span class="p">,</span>
<span class="p">});</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">router</span><span class="p">;</span>
</code></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>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>vue add e2e-playwright <span class="nt">--dev</span>
</code></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>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">const</span> <span class="p">{</span> <span class="nx">chromium</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">playwright</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">expect</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">chai</span><span class="dl">'</span><span class="p">);</span>
</code></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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">baseUrl</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">http://localhost:8080/</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">adminPassword</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Super_Secure_Pass</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">adminUserName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">normalUserName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">normalUserPassword</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Super_Secure_Pass</span><span class="dl">'</span><span class="p">;</span>
</code></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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Authenticated Vue App: </span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">browser</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">page</span><span class="p">;</span>
<span class="p">})</span>
</code></pre>
</div>
<p>Now you would need to create an instance of your browser and page. So go ahead and add a <code>beforeEach</code> method. Inside that, lunch your browser, create a new page and navigate to your home page:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">before</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">browser</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">chromium</span><span class="p">.</span><span class="nx">launch</span><span class="p">();</span>
<span class="nx">page</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="nx">baseUrl</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
</div>
<p>Make sure you close these objects at the end of the tests via an <code>after</code> method:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">after</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="p">});</span>
</code></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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">An unauthenticated user should not be able to see the admin page</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">baseUrl</span><span class="p">}</span><span class="s2">admin`</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">url</span><span class="p">()).</span><span class="nx">to</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">baseUrl</span><span class="p">}</span><span class="s2">login?returnUrl=/admin`</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
</div>
<p>If you now run the tests by running <code>yarn test:e2e</code>, you should see the test pass.</p>
<h3>
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>Promise.all</code> method to be able to get a reference to your popup window:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">popup</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span>
<span class="nx">page</span><span class="p">.</span><span class="nx">waitForEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">popup</span><span class="dl">'</span><span class="p">),</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="dl">'</span><span class="s1">a.signup</span><span class="dl">'</span><span class="p">)</span>
<span class="p">]);</span>
</code></pre>
</div>
<p>Now that you have the reference, you need to fill in the info and click on the login:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="k">await</span> <span class="nx">popup</span><span class="p">.</span><span class="nx">fill</span><span class="p">(</span><span class="dl">'</span><span class="s1">input[type="email"]</span><span class="dl">'</span><span class="p">,</span> <span class="nx">adminUserName</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">popup</span><span class="p">.</span><span class="nx">fill</span><span class="p">(</span><span class="dl">'</span><span class="s1">input[type="password"]</span><span class="dl">'</span><span class="p">,</span> <span class="nx">adminPassword</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">popup</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="dl">'</span><span class="s1">button[type="submit"]</span><span class="dl">'</span><span class="p">);</span>
</code></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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">be redirected back to admin page after login</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">baseUrl</span><span class="p">}</span><span class="s2">admin`</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">popup</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span>
<span class="nx">page</span><span class="p">.</span><span class="nx">waitForEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">popup</span><span class="dl">'</span><span class="p">),</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="dl">'</span><span class="s1">a.signup</span><span class="dl">'</span><span class="p">)</span>
<span class="p">]);</span>
<span class="nx">popup</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">close</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">url</span><span class="p">()).</span><span class="nx">to</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">baseUrl</span><span class="p">}</span><span class="s2">admin`</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">await</span> <span class="nx">popup</span><span class="p">.</span><span class="nx">fill</span><span class="p">(</span><span class="dl">'</span><span class="s1">input[type="email"]</span><span class="dl">'</span><span class="p">,</span> <span class="nx">adminUserName</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">popup</span><span class="p">.</span><span class="nx">fill</span><span class="p">(</span><span class="dl">'</span><span class="s1">input[type="password"]</span><span class="dl">'</span><span class="p">,</span> <span class="nx">adminPassword</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">popup</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="dl">'</span><span class="s1">button[type="submit"]</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
</div>
<p>The reason why you'd need a <code>waitForEvent</code> method in the <code>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>
Full code
</h2>
<p>You can find the full source code on my <a href="proxy.php?url=https://github.com/yashints/vue-e2e-playwright">GitHub repository here</a>.</p>
<h2>
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>
webdev
showdev
vue
playwright
-
How to use Azure Cosmos Emulator as a local MongoDb database π
Yaser Adel Mehraban
Wed, 30 Dec 2020 06:00:40 +0000
https://dev.to/azure/how-to-use-azure-cosmos-emulator-as-a-local-mongodb-database-2gc6
https://dev.to/azure/how-to-use-azure-cosmos-emulator-as-a-local-mongodb-database-2gc6
<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="proxy.php?url=https://yas.fyi/azcosemu" rel="noopener noreferrer">Azure Cosmos DB Emulator</a> installed and Cosmos DB supports MongoDb APIs.</p>
<h2>
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="proxy.php?url=https://aka.ms/cosmosdb-emulator" rel="noopener noreferrer">over here and install the latest version</a>. If you stumbled upon any issues use <a href="proxy.php?url=https://docs.microsoft.com/en-us/azure/cosmos-db/troubleshoot-local-emulator" rel="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>
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>EnableMongoDbEndpoint</code> argument. </p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp0ef0v7dal9krd3ir6uz.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp0ef0v7dal9krd3ir6uz.jpg" alt="Starting Azure Cosmos DB Emulator"></a></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="proxy.php?url=https://github.com/microsoft/terminal" rel="noopener noreferrer">Windows Terminal</a>. Run the below command to fire up the emulator with MongoDB support:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>& <span class="s1">'C:\Program Files\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe'</span> /EnableMongoDbEndpoint
</code></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>
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><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5jgp1t4v6rvo90smkbgr.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5jgp1t4v6rvo90smkbgr.jpg" alt="Azure Cosmos DB Emulator connection string page"></a></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>
Creating a Node.js Web API project
</h2>
<p>Open up a command prompt in a new folder and initiate a project:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>npm init <span class="nt">-y</span>
</code></pre>
</div>
<p>The next thing to do is to install the dependencies:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="c"># linux or macOS</span>
<span class="nb">touch </span>app.js
<span class="c"># Windows</span>
<span class="nb">echo</span> <span class="s2">""</span> <span class="o">></span> app.js
<span class="c"># install dependencies</span>
npm <span class="nb">install </span>express mongodb body-parser
</code></pre>
</div>
<h3>
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.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">Express</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">express</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">BodyParser</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">body-parser</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">MongoClient</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">mongodb</span><span class="dl">"</span><span class="p">).</span><span class="nx">MongoClient</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">ObjectId</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">mongodb</span><span class="dl">"</span><span class="p">).</span><span class="nx">ObjectID</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nc">Express</span><span class="p">();</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="nx">BodyParser</span><span class="p">.</span><span class="nf">json</span><span class="p">());</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="nx">BodyParser</span><span class="p">.</span><span class="nf">urlencoded</span><span class="p">({</span> <span class="na">extended</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}));</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">listen</span><span class="p">(</span><span class="mi">5000</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{});</span>
</code></pre>
</div>
<p>What we have here is a simple web server which responds to <code>http://localhost:5000</code>. All we're doing is initializing the <code>MongoClient</code> and a web server. Next thing we need to do is to establish a connection with MongoDB interface of our emulator:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">Express</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">express</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">BodyParser</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">body-parser</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">MongoClient</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">mongodb</span><span class="dl">"</span><span class="p">).</span><span class="nx">MongoClient</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">ObjectId</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">mongodb</span><span class="dl">"</span><span class="p">).</span><span class="nx">ObjectID</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">CONNECTION_URL</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">YOUR CONNECTION STRING</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">DATABASE_NAME</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">cosmos_emulator_mongo</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nc">Express</span><span class="p">();</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="nx">BodyParser</span><span class="p">.</span><span class="nf">json</span><span class="p">());</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="nx">BodyParser</span><span class="p">.</span><span class="nf">urlencoded</span><span class="p">({</span> <span class="na">extended</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}));</span>
<span class="kd">var</span> <span class="nx">database</span><span class="p">,</span> <span class="nx">collection</span><span class="p">;</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">listen</span><span class="p">(</span><span class="mi">5000</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">MongoClient</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="nx">CONNECTION_URL</span><span class="p">,</span> <span class="p">{</span> <span class="na">useNewUrlParser</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">client</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">database</span> <span class="o">=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">db</span><span class="p">(</span><span class="nx">DATABASE_NAME</span><span class="p">);</span>
<span class="nx">collection</span> <span class="o">=</span> <span class="nx">database</span><span class="p">.</span><span class="nf">collection</span><span class="p">(</span><span class="dl">"</span><span class="s2">celebrities</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Connected to `</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">DATABASE_NAME</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">`!</span><span class="dl">"</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
</code></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>package.json</code>:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node app.js"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>If you run <code>npm start</code> now, you should see the message connected to <code>Connected to 'cosmos_emulator_mongo'</code> printed on your console which means you've successfully connected to the MongoDb APIs.</p>
<h3>
Seed the database
</h3>
<p>First let's create a JSON file which contains some celebrities π:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">[{</span><span class="w">
</span><span class="nl">"age"</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_place"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Harij, Gujarat, India"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_sign"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Pisces"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_year"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1996"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birthday"</span><span class="p">:</span><span class="w"> </span><span class="s2">"September 28"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Daxeel Soni"</span><span class="p">,</span><span class="w">
</span><span class="nl">"occupation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Developer of this API. More: www.daxeelsoni.in"</span><span class="p">,</span><span class="w">
</span><span class="nl">"photo_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://daxeelsoni.in/images/me.jpg"</span><span class="w">
</span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"age"</span><span class="p">:</span><span class="w"> </span><span class="mi">28</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_place"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Houston"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_sign"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Libra"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_year"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1987"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birthday"</span><span class="p">:</span><span class="w"> </span><span class="s2">"September 28"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Hilary Duff"</span><span class="p">,</span><span class="w">
</span><span class="nl">"occupation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TV Actress"</span><span class="p">,</span><span class="w">
</span><span class="nl">"photo_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.famousbirthdays.com/thumbnails/duff-hilary-medium.jpg"</span><span class="w">
</span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"age"</span><span class="p">:</span><span class="w"> </span><span class="mi">38</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_place"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Columbia"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_sign"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Libra"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_year"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1977"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birthday"</span><span class="p">:</span><span class="w"> </span><span class="s2">"September 28"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Young Jeezy"</span><span class="p">,</span><span class="w">
</span><span class="nl">"occupation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Rapper"</span><span class="p">,</span><span class="w">
</span><span class="nl">"photo_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.famousbirthdays.com/thumbnails/jeezy-young-medium.jpg"</span><span class="w">
</span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"age"</span><span class="p">:</span><span class="w"> </span><span class="mi">48</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_place"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Roanoke"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_sign"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Libra"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birth_year"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1967"</span><span class="p">,</span><span class="w">
</span><span class="nl">"birthday"</span><span class="p">:</span><span class="w"> </span><span class="s2">"September 28"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Challen Cates"</span><span class="p">,</span><span class="w">
</span><span class="nl">"occupation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TV Actress"</span><span class="p">,</span><span class="w">
</span><span class="nl">"photo_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.famousbirthdays.com/thumbnails/cates-challen-medium.jpg"</span><span class="w">
</span><span class="p">}]</span><span class="w">
</span></code></pre>
</div>
<p>Now that we have our JSON file, let's seed the database. Replace the initial connect code with this:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">app</span><span class="p">.</span><span class="nf">listen</span><span class="p">(</span><span class="mi">5000</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">MongoClient</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span>
<span class="nx">CONNECTION_URL</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">useNewUrlParser</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">sslValidate</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">client</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">database</span> <span class="o">=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">db</span><span class="p">(</span><span class="nx">DATABASE_NAME</span><span class="p">);</span>
<span class="nx">collection</span> <span class="o">=</span> <span class="nx">database</span><span class="p">.</span><span class="nf">collection</span><span class="p">(</span><span class="dl">"</span><span class="s2">celebrities</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">collection</span><span class="p">.</span><span class="nf">estimatedDocumentCount</span><span class="p">({},</span> <span class="p">(</span><span class="nx">erorr</span><span class="p">,</span> <span class="nx">numOfRecords</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">numOfRecords</span> <span class="o"><=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">fs</span><span class="p">.</span><span class="nf">readFile</span><span class="p">(</span><span class="dl">"</span><span class="s2">./info.json</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Error reading file from disk: </span><span class="p">${</span><span class="nx">err</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// parse JSON string to JSON object</span>
<span class="kd">const</span> <span class="nx">celebrities</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="nx">collection</span><span class="p">.</span><span class="nf">insertMany</span><span class="p">(</span><span class="nx">celebrities</span><span class="p">,</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Error in saving seed data: </span><span class="p">${</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Seed data inserted into the database!`</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Connected to database`</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">);</span>
<span class="p">});</span>
</code></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>const fs = require('fs')</code> at the top with other require statements.</p>
<p>Now if you run <code>npm start</code> would should see the application start and then the <strong>seed data inserted</strong> message.</p>
<h3>
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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">app</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/api/celebrities</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">collection</span><span class="p">.</span><span class="nf">find</span><span class="p">({}).</span><span class="nf">toArray</span><span class="p">((</span><span class="nx">error</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nf">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nf">send</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">response</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre>
</div>
<h2>
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>http://localhost:5000/api/celebrities</code> or just use the below command:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">-X</span> GET http://localhost:5000/api/celebrities
</code></pre>
</div>
<h2>
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="proxy.php?url=https://github.com/yashints/azure-cosmosdb-emulator-mongodb" rel="noopener noreferrer">GitHub repository here</a>.</p>
azure
cosmosdb
emulator
mongodb
-
How much do you know about Azure CosmosDB Emulator? π§
Yaser Adel Mehraban
Wed, 30 Dec 2020 01:13:34 +0000
https://dev.to/azure/how-much-do-you-know-about-azure-cosmosdb-emulator-1mkf
https://dev.to/azure/how-much-do-you-know-about-azure-cosmosdb-emulator-1mkf
<p><a href="proxy.php?url=https://azure.microsoft.com/en-au/services/cosmos-db/">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="proxy.php?url=https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator">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>
<h2>
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="proxy.php?url=https://azure.microsoft.com/en-us/free/">Free Trial Account</a> which gives you <strong>$200 dollars</strong> to spend over a year, or even the <a href="proxy.php?url=https://www.documentdb.com/sql/demo">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>
Features
</h2>
<h3>
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>
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>
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>
Installation
</h2>
<p>Simply <a href="proxy.php?url=https://aka.ms/cosmosdb-emulator">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="proxy.php?url=https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=cli%2Cssl-netstd21#run-on-windows-docker">the official documentation</a>.</p>
<p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--HK2YI_V0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i2i7ax6e4xkwqyqurp4o.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--HK2YI_V0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i2i7ax6e4xkwqyqurp4o.jpg" alt="Cosmos DB Emulator"></a></p>
<h2>
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="proxy.php?url=https://docs.microsoft.com/en-us/azure/cosmos-db/estimate-ru-with-capacity-planner">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>
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>
azure
showdev
cosmosdb
emulator
-
Access your clipboard on your other devices ππ
Yaser Adel Mehraban
Fri, 13 Nov 2020 02:57:50 +0000
https://dev.to/yashints/access-your-clipboard-on-your-other-devices-1b9o
https://dev.to/yashints/access-your-clipboard-on-your-other-devices-1b9o
<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>
<h2>
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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--298VJezA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6gx0qrxp9gvxhc6oyabi.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--298VJezA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6gx0qrxp9gvxhc6oyabi.jpg" alt="Opening clipboard settings on windows"></a></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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--SHgZDnPS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zwolf1pk0c9s2erc9zf8.JPG" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--SHgZDnPS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zwolf1pk0c9s2erc9zf8.JPG" alt="Clipboard settings on windows"></a></p>
<p>While you're at it, turn clipboard history on too to have a beautiful clipboard history available by pressing Win+V.</p>
<p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--h6phORrW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uytculzak075uaww7auc.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--h6phORrW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uytculzak075uaww7auc.jpg" alt="Windows clipboard history"></a></p>
<h3>
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 Win+V to open up your clipboard history and select what you want to be synced across your devices after copying the items.</p>
</blockquote>
<h3>
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><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--KTIpXom1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0ea3yfayaih3gnffr2ks.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--KTIpXom1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0ea3yfayaih3gnffr2ks.jpg" alt="Pin items in clipboard history on windows"></a></p>
<h2>
Mac to Mac
</h2>
<p>If you're a Mac user and want to achieve the same, you can use the <a href="proxy.php?url=https://support.apple.com/en-au/HT209460#:~:text=On%20your%20Mac%3A%20Choose%20Apple,Handoff%2C%20then%20turn%20on%20Handoff.">Universal Clipboard</a>.</p>
<p>It's really simple, like we saw on windows setup.</p>
<h2>
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="proxy.php?url=https://pastebin.com/">PASTEBIN</a>.</p>
<p>This is a web tool that allows you to share your content across different devices with ease.</p>
<h2>
Warning
</h2>
<p>When using third party software whether it's application or web app, never share secrets or your personal information.</p>
<h2>
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>
productivity
windows
showdev
clipboard
-
I've joined Microsoft as an Azure Technical Trainer πΊοΈ π
Yaser Adel Mehraban
Mon, 02 Nov 2020 04:04:18 +0000
https://dev.to/yashints/i-ve-joined-microsoft-as-an-azure-technical-trainer-5e72
https://dev.to/yashints/i-ve-joined-microsoft-as-an-azure-technical-trainer-5e72
<p>The cat is out of the bag now, I've joined <a href="proxy.php?url=https://www.microsoft.com/">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 π. </p>
<h2>
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="proxy.php?url=https://en.wikipedia.org/wiki/QBasic#:~:text=QBasic%20is%20an%20integrated%20development,on%20demand%20within%20the%20IDE.">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="proxy.php?url=https://www.microsoft.com/">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="proxy.php?url=https://en.wikipedia.org/wiki/C%2B%2B">C++</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/Turbo_Pascal">Turbo Pascal</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/FoxPro">FoxPro</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/Delphi_(software)">Delphi</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/Active_Server_Pages">ASP Classic</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/PHP">PHP</a>, and many other languages and ended up with, and finally <a href="proxy.php?url=https://en.wikipedia.org/wiki/ASP.NET_MVC">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="proxy.php?url=https://en.wikipedia.org/wiki/ASP.NET_Web_Forms">ASP.Net web forms</a> and <a href="proxy.php?url=https://en.wikipedia.org/wiki/Spring_Framework">Java Spring</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/Java_Card">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>
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>
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>
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>
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="proxy.php?url=https://www.linkedin.com/company/readify/?originalSubdomain=au">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>
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>
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>
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>
career
dreamjob
story
journey
-
Get started with Vue 3 and Tailwindcss π
Yaser Adel Mehraban
Wed, 21 Oct 2020 23:53:39 +0000
https://dev.to/yashints/get-started-with-vue-3-and-tailwindcss-63m
https://dev.to/yashints/get-started-with-vue-3-and-tailwindcss-63m
<p>Last week I wanted to setup a <a href="proxy.php?url=https://v3.vuejs.org/" rel="noopener noreferrer">Vue.js v3</a> app with <a href="proxy.php?url=https://tailwindcss.com/" rel="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="proxy.php?url=">postcss</a> to get your setup working, but with new version of <strong>Vue</strong>, the <code>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>
<h2>
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="proxy.php?url=https://cli.vuejs.org/" rel="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:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>npm <span class="nb">install</span> <span class="nt">-g</span> @vue/cli
<span class="c"># OR</span>
yarn global add @vue/cli
</code></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>
Creating the project
</h2>
<p>In order to create your project, you need to call the CLI and give your project name:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>vue create vue-tailwindcss
</code></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><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbb9cqc4nxnepp83dqeu1.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbb9cqc4nxnepp83dqeu1.jpg" alt="Vue CLI wizard"></a></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><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqrf04mw7z6b52a11txg4.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqrf04mw7z6b52a11txg4.jpg" alt="Vue CLI wizard"></a></p>
<p>From here press Entre on <strong>Choose Vue version</strong>, and select <strong>3.x (Preview)</strong>.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv03tyernevldgpn2289i.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv03tyernevldgpn2289i.jpg" alt="Vue CLI wizard"></a></p>
<p>I would normally choose <a href="proxy.php?url=https://www.typescriptlang.org/" rel="noopener noreferrer">TypeScript</a>, <a href="proxy.php?url=https://babeljs.io/" rel="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="proxy.php?url=https://code.visualstudio.com/" rel="noopener noreferrer">VS Code</a>.</p>
<h2>
Installing the required dependencies
</h2>
<p>At this point we need to install Tailwindcss:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>npm <span class="nb">install </span>tailwindcss
</code></pre>
</div>
<h2>
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>main.js</code> for convinience. </p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkqweaukoaxjc9edahmdw.JPG" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkqweaukoaxjc9edahmdw.JPG" alt="Main style file"></a></p>
<p>Don't forget to import it inside your main.js file:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="k">import</span> <span class="dl">"</span><span class="s2">./main.css</span><span class="dl">"</span><span class="p">;</span>
<span class="c1">// ...</span>
</code></pre>
</div>
<p>And now is the time to import the <strong>Tailwincss</strong> base and components in our <code>css</code> file:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight css"><code><span class="c">/* src/main.css */</span>
<span class="k">@import</span> <span class="s1">"tailwindcss/base"</span><span class="p">;</span>
<span class="k">@import</span> <span class="s1">"tailwindcss/components"</span><span class="p">;</span>
<span class="k">@import</span> <span class="s1">"tailwindcss/utilities"</span><span class="p">;</span>
</code></pre>
</div>
<h2>
Postcss configuration
</h2>
<p>Many articles tell you to create a <code>postcss.config.js</code> or <code>.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>package.json</code> file:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="nl">"postcss"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"plugins"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"tailwindcss"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span><span class="w">
</span><span class="nl">"autoprefixer"</span><span class="p">:</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<h2>
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="proxy.php?url=https://tailwindui.com/preview" rel="noopener noreferrer">their free gallery</a> in:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!--
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="nt"><div</span> <span class="na">class=</span><span class="s">"relative bg-white overflow-hidden"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"max-w-screen-xl mx-auto"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"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><span class="nt">></span>
<span class="nt"><svg</span> <span class="na">class=</span><span class="s">"hidden lg:block absolute right-0 inset-y-0 h-full w-48 text-white transform translate-x-1/2"</span> <span class="na">fill=</span><span class="s">"currentColor"</span> <span class="na">viewBox=</span><span class="s">"0 0 100 100"</span> <span class="na">preserveAspectRatio=</span><span class="s">"none"</span><span class="nt">></span>
<span class="nt"><polygon</span> <span class="na">points=</span><span class="s">"50,0 100,0 50,100 0,100"</span> <span class="nt">/></span>
<span class="nt"></svg></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"relative pt-6 px-4 sm:px-6 lg:px-8"</span><span class="nt">></span>
<span class="nt"><nav</span> <span class="na">class=</span><span class="s">"relative flex items-center justify-between sm:h-10 lg:justify-start"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"flex items-center flex-grow flex-shrink-0 lg:flex-grow-0"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"flex items-center justify-between w-full md:w-auto"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">aria-label=</span><span class="s">"Home"</span><span class="nt">></span>
<span class="nt"><img</span> <span class="na">class=</span><span class="s">"h-8 w-auto sm:h-10"</span> <span class="na">src=</span><span class="s">"https://tailwindui.com/img/logos/workflow-mark-on-white.svg"</span> <span class="na">alt=</span><span class="s">"Logo"</span><span class="nt">></span>
<span class="nt"></a></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"-mr-2 flex items-center md:hidden"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">class=</span><span class="s">"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> <span class="na">id=</span><span class="s">"main-menu"</span> <span class="na">aria-label=</span><span class="s">"Main menu"</span> <span class="na">aria-haspopup=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><svg</span> <span class="na">class=</span><span class="s">"h-6 w-6"</span> <span class="na">stroke=</span><span class="s">"currentColor"</span> <span class="na">fill=</span><span class="s">"none"</span> <span class="na">viewBox=</span><span class="s">"0 0 24 24"</span><span class="nt">></span>
<span class="nt"><path</span> <span class="na">stroke-linecap=</span><span class="s">"round"</span> <span class="na">stroke-linejoin=</span><span class="s">"round"</span> <span class="na">stroke-width=</span><span class="s">"2"</span> <span class="na">d=</span><span class="s">"M4 6h16M4 12h16M4 18h16"</span> <span class="nt">/></span>
<span class="nt"></svg></span>
<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"hidden md:block md:ml-10 md:pr-4"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out"</span><span class="nt">></span>Product<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"ml-8 font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out"</span><span class="nt">></span>Features<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"ml-8 font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out"</span><span class="nt">></span>Marketplace<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"ml-8 font-medium text-gray-500 hover:text-gray-900 transition duration-150 ease-in-out"</span><span class="nt">></span>Company<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"ml-8 font-medium text-indigo-600 hover:text-indigo-900 transition duration-150 ease-in-out"</span><span class="nt">></span>Log in<span class="nt"></a></span>
<span class="nt"></div></span>
<span class="nt"></nav></span>
<span class="nt"></div></span>
<span class="c"><!--
Mobile menu, show/hide based on menu open state.
Entering: "duration-150 ease-out"
From: "opacity-0 scale-95"
To: "opacity-100 scale-100"
Leaving: "duration-100 ease-in"
From: "opacity-100 scale-100"
To: "opacity-0 scale-95"
--></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"absolute top-0 inset-x-0 p-2 transition transform origin-top-right md:hidden"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"rounded-lg shadow-md"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"rounded-lg bg-white shadow-xs overflow-hidden"</span> <span class="na">role=</span><span class="s">"menu"</span> <span class="na">aria-orientation=</span><span class="s">"vertical"</span> <span class="na">aria-labelledby=</span><span class="s">"main-menu"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"px-5 pt-4 flex items-center justify-between"</span><span class="nt">></span>
<span class="nt"><div></span>
<span class="nt"><img</span> <span class="na">class=</span><span class="s">"h-8 w-auto"</span> <span class="na">src=</span><span class="s">"https://tailwindui.com/img/logos/workflow-mark-on-white.svg"</span> <span class="na">alt=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"-mr-2"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">class=</span><span class="s">"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> <span class="na">aria-label=</span><span class="s">"Close menu"</span><span class="nt">></span>
<span class="nt"><svg</span> <span class="na">class=</span><span class="s">"h-6 w-6"</span> <span class="na">stroke=</span><span class="s">"currentColor"</span> <span class="na">fill=</span><span class="s">"none"</span> <span class="na">viewBox=</span><span class="s">"0 0 24 24"</span><span class="nt">></span>
<span class="nt"><path</span> <span class="na">stroke-linecap=</span><span class="s">"round"</span> <span class="na">stroke-linejoin=</span><span class="s">"round"</span> <span class="na">stroke-width=</span><span class="s">"2"</span> <span class="na">d=</span><span class="s">"M6 18L18 6M6 6l12 12"</span> <span class="nt">/></span>
<span class="nt"></svg></span>
<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"px-2 pt-2 pb-3"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"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"</span> <span class="na">role=</span><span class="s">"menuitem"</span><span class="nt">></span>Product<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"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"</span> <span class="na">role=</span><span class="s">"menuitem"</span><span class="nt">></span>Features<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"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"</span> <span class="na">role=</span><span class="s">"menuitem"</span><span class="nt">></span>Marketplace<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"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"</span> <span class="na">role=</span><span class="s">"menuitem"</span><span class="nt">></span>Company<span class="nt"></a></span>
<span class="nt"></div></span>
<span class="nt"><div></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"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"</span> <span class="na">role=</span><span class="s">"menuitem"</span><span class="nt">></span>
Log in
<span class="nt"></a></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><main</span> <span class="na">class=</span><span class="s">"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"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"sm:text-center lg:text-left"</span><span class="nt">></span>
<span class="nt"><h2</span> <span class="na">class=</span><span class="s">"text-4xl tracking-tight leading-10 font-extrabold text-gray-900 sm:text-5xl sm:leading-none md:text-6xl"</span><span class="nt">></span>
Data to enrich your
<span class="nt"><br</span> <span class="na">class=</span><span class="s">"xl:hidden"</span><span class="nt">></span>
<span class="nt"><span</span> <span class="na">class=</span><span class="s">"text-indigo-600"</span><span class="nt">></span>online business<span class="nt"></span></span>
<span class="nt"></h2></span>
<span class="nt"><p</span> <span class="na">class=</span><span class="s">"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"</span><span class="nt">></span>
Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.
<span class="nt"></p></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-start"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"rounded-md shadow"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"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"</span><span class="nt">></span>
Get started
<span class="nt"></a></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"mt-3 sm:mt-0 sm:ml-3"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"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"</span><span class="nt">></span>
Live demo
<span class="nt"></a></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></main></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2"</span><span class="nt">></span>
<span class="nt"><img</span> <span class="na">class=</span><span class="s">"h-56 w-full object-cover sm:h-72 md:h-96 lg:w-full lg:h-full"</span> <span class="na">src=</span><span class="s">"https://images.unsplash.com/photo-1551434678-e076c223a692?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80"</span> <span class="na">alt=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
</code></pre>
</div>
<h2>
Run the application
</h2>
<p>You're all set, run <code>npm run serve</code> and when the CLI is finished compiling the app, open a browser window and head to <code>https://localhost:8080</code> to see the <code>Tailwindcss</code> component:</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu3qacbxhvgdfeeutdvdp.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu3qacbxhvgdfeeutdvdp.jpg" alt="Tailwindcss components"></a></p>
<h2>
Customisation
</h2>
<p>If you want to customise any of the default styles, you need to modify the theme. To do so, run:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>npx tailwind init
</code></pre>
</div>
<p>This will create a <code>tailwind.config.js</code> file for you in the root of your directory. To demo an example customisation, let's say we want to add some space between the photo and the right hand side of the page. Currently the photo is having an <code>position:absolute</code> and <code>right:0</code> because it has the <code>lg:right-0</code> class.</p>
<p>So we want to add a new class which has <code>10%</code> margin on the right hand side of the image. For that to work, we will need below code in our <code>tailwindcss.config.js</code>:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">future</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">// removeDeprecatedGapUtilities: true,</span>
<span class="c1">// purgeLayersByDefault: true,</span>
<span class="p">},</span>
<span class="na">purge</span><span class="p">:</span> <span class="p">[],</span>
<span class="na">theme</span><span class="p">:</span> <span class="p">{</span>
<span class="na">inset</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="na">auto</span><span class="p">:</span> <span class="dl">'</span><span class="s1">auto</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">1/10</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">10%</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">extend</span><span class="p">:</span> <span class="p">{},</span>
<span class="p">},</span>
<span class="na">variants</span><span class="p">:</span> <span class="p">{},</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[],</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Pay attention that I've added a <code>1/10</code> with the value of <code>10%</code> which we can use. Now all we need to do is to add <code>lg:right-1/10</code> to the image container which pushes the image to the left by <em>10%</em>:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"lg:absolute lg:inset-y-0 lg:right-1/10 lg:w-1/2"</span><span class="nt">></span>
<span class="nt"></div></span>
</code></pre>
</div>
<p>And now it should look like this:</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpy2bk9086uj42ohe3t5o.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpy2bk9086uj42ohe3t5o.jpg" alt="Customise Tailwindcss"></a></p>
<h2>
Summary
</h2>
<p>We saw how to add <code>Tailwindcss</code> library to our Vue 3 application created by Vue CLI. Hopefully this will help some of you out there facing the same issue as me. See you next time ππ½.</p>
<p>You can find the code in my <a href="proxy.php?url=https://github.com/yashints/Vue-Tailwind" rel="noopener noreferrer">GitHub repository</a>.</p>
showdev
webdev
vue
tailwindcss