<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Matt Mascioni</title>
    <description>The latest articles on Forem by Matt Mascioni (@mmascioni).</description>
    <link>https://forem.com/mmascioni</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392047%2F51a13b2a-7560-45f6-b1aa-089aa1db6858.jpg</url>
      <title>Forem: Matt Mascioni</title>
      <link>https://forem.com/mmascioni</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mmascioni"/>
    <language>en</language>
    <item>
      <title>Switching from Customer Success to Engineering: a year later</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Tue, 28 Feb 2023 21:22:37 +0000</pubDate>
      <link>https://forem.com/mmascioni/switching-from-customer-success-to-engineering-a-year-later-33da</link>
      <guid>https://forem.com/mmascioni/switching-from-customer-success-to-engineering-a-year-later-33da</guid>
      <description>&lt;p&gt;A couple years ago, I joined &lt;a href="https://partnerstack.com/" rel="noopener noreferrer"&gt;PartnerStack&lt;/a&gt; in their Customer Success department, as an integration specialist. A lot has changed since then — I’ve since transitioned into our engineering department, and it’s been an incredible ride along the way. After over a year in engineering, I wanted to share a little bit about how I got there, and offer some advice to anyone looking to make a similar move at their organization.&lt;/p&gt;

&lt;p&gt;When I joined PartnerStack on the integrations team back in 2020, I did with the intent of eventually becoming a software engineer, as the role felt like a good medium between being product facing and being customer facing. I've loved writing software for a long time, but at the time I didn't have any traditional software engineering education. My education was in chemical engineering, and a lot of what I learned about web development had been on my own, through side projects, online courses, or freelancing in the past. Not having traditional or bootcamp education made it trickier to break into a technical role in tech, even more in 2020 as the COVID-19 pandemic had just begun. After going through hundreds of rejected job applications and interviews, the PartnerStack interview process felt like a breath of fresh air and I was delighted to join the team.&lt;/p&gt;

&lt;p&gt;The integrations team is an interesting one: we primarily helped during the &lt;strong&gt;onboarding&lt;/strong&gt; phase of a customer’s journey, which included consulting on how best to use our APIs and SDKs with other engineering teams, using our integrations platform to build integrations with a customer’s systems, and more. You were really the main technical point of contact throughout the journey. We also interfaced with and supported other teams, both within CS and outside of it. You had exposure to product and engineering teams as well to relay client feedback and address bugs. Being on the team was an incredible way to learn both the product and the people who used it deeply, and some of the fondest memories I have at the company were while I was there. The team was brand new back in 2020, I onboarded with the only other team member as the company was beginning to build the role out.&lt;/p&gt;

&lt;p&gt;A while after joining, I started working with my manager to flesh out my goals of transitioning and how I could go about achieving them eventually. Even though I had support to carve out this career path, execution was difficult. Often times the process felt paralyzing, because I wasn’t sure where to start. I was already confident in the primary language we used on our back-end (Python), but wanted to begin gaining exposure to the codebase and working on issues. At the same time I didn’t want to step on any toes and take up other team’s time / de-rail any processes I didn’t know about.&lt;/p&gt;

&lt;p&gt;Where I started was following a bit of a “one step up” model — go “one step up” from what you’re currently doing to dip in to what another team is working on. For me, the target was our integrations platform — I already worked extensively with it (and the APIs backing it) as someone customer-facing. One step up would be working with the team who managed that platform from an engineering perspective, so I got in touch with the manager of that team and some of the engineers who worked there. We settled on a model where I could take on backlog tickets or bugs (i.e. nothing in a planned or active sprint, but where a user need was validated) from their team, pairing with their engineers where needed.&lt;/p&gt;

&lt;p&gt;A lot of these tickets or bugs were “first time” type issues that would normally be handed off to new developers onboarding. The bugs forced me to understand the path of execution from a code perspective for features I was used to seeing from a user’s perspective, and even though the fixes were a couple lines long, they were monumental in helping me understand our back-end (largely a monolith). Over the coming months, I took on bigger tickets, eventually helping close out a whole feature. It felt amazing being able to share what I had built with my team but also the customers I was on call with — it became a really nice feedback loop.&lt;/p&gt;

&lt;p&gt;The challenge became balancing these three sets of responsibilities: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building out team processes and preparing the team to grow&lt;/li&gt;
&lt;li&gt;The day-to-day responsibilities of being on the integrations team — being customer-facing can be demanding!&lt;/li&gt;
&lt;li&gt;The responsibilities I was taking on as part of picking up and completing engineering tickets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The balancing act was a tough one, but a few things really helped out. Firstly, we began hiring on the team. This made building out processes much more important, but it really helped in the long run as team members onboarded faster. A nice side effect of this was that &lt;strong&gt;other&lt;/strong&gt; teammates could now help take on some of the things we were working on if they were interested, since they were now public and not living in people’s brains. The other thing that helped was strictly not working on tickets within an engineering team’s sprint — this kept expectations low and didn’t affect their delivery as much while giving me a ton of space to learn. If I needed to de-prioritize a ticket, I totally could.&lt;/p&gt;

&lt;p&gt;Eventually, after numerous conversations and plans being drawn up I was offered the position and started in late 2021. Along the way, I learned a few things that I thought might be useful to someone else wanting to make the switch down the line. None of these are meant to be prescriptive, every workplace and situation is unique and mine pertains more to a  SaaS startup.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep notes of the journey:&lt;/strong&gt; Keep track of the details as you go through all of this — what did you work on? What sucked? What conversations with which people did you have? These notes, &lt;a href="https://jvns.ca/blog/brag-documents/" rel="noopener noreferrer"&gt;a lot like a brag document&lt;/a&gt;, help you advocate for yourself as you discuss your goals with your supporter, and supporters within the engineering org. Adding some dates in will help you look back and feel like you made progress on days where you feel you didn’t. I presented my story linearly, but this process is anything but linear, making these useful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Communicate often and advocate for yourself:&lt;/strong&gt; Your manager/supporter is there to advocate for you, but they can’t do so until you make your goals known! Communicate with them from the beginning to build a plan, and communicate with managers/team leads on teams you’re interested in learning more about-- this isn’t something you do on your own. Also keep them up to date on your progress (this is where keeping notes comes in handy!) One misconception I had when I started was that wins would speak for themself, but they commonly don’t. By sharing your wins/losses, and speaking openly about the work you’d like to take on and where you want to orient your career, you’re advocating for yourself and making every conversation a little easier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Immerse yourself a bit:&lt;/strong&gt; One thing that helped me a ton was joining the public Slack channels of engineering teams I was interested in working with (if they were cool with it). This gave me insight into the “stream” of work that team was producing; if there were requests for PR reviews, I got to check out the PR and learn from how existing teammates organized their code. If there are regular, open-invite, cross-team meetings that take place for engineering teams at your company, look into attending them as a fly on the wall. These meetings might contain high level feature proposals, which may be easier to digest since they need to be created for a broader audience. The discussions are arguably the best part, and the kinds of questions raised can help prep you for code review opportunities you’ll have down the line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Try to onboard on your own:&lt;/strong&gt; Ask around and find whatever onboarding documentation you can on how an engineer would onboard onto the team you were interested in working with. Request access to and set up the repos locally yourself, keeping a list of questions for when you get stuck. The benefits of this are multi-fold:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You uncover holes in the onboarding documentation, which engineering leaders will appreciate (a fresh pair of eyes is underrated)&lt;/li&gt;
&lt;li&gt;You’ll uncover frameworks you don’t know yet, adding to your list of things to start learning to prepare you to take on tickets&lt;/li&gt;
&lt;li&gt;You’ll have your own copy of (at least a part of) your product running locally, which will benefit your current team immensely as eventually you’ll be able to replicate customer scenarios locally and answer customer/team questions even faster. This is extremely beneficial for “legacy” features few people have context on.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;One foot in your comfort zone, one foot out:&lt;/strong&gt; I don’t have a better name for this, but when looking at work to take on, first try engineering work thats as closely as possible related to what you work on in your role. This lets you use the context you have from your role to give you a leg to stand on. Some examples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you’re an integration/implementation specialist who regularly talks about specific features or integrations look to see if there are any reported bugs on those features, or “quick wins” improvements one of the engineering teams has planned.&lt;/li&gt;
&lt;li&gt;If you’re on a support team, talk to an engineering team member looking into a bugfix for a bug you reported, as it might be a good opportunity for a pairing or shadowing session.&lt;/li&gt;
&lt;li&gt;If you’re a customer success manager, try shadowing with a support / integrations team member as they solve an issue or solution for one of your customers. &lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comb through engineering team backlogs:&lt;/strong&gt; Every engineering team has a backlog of things that they haven’t been able to prioritize yet. If these backlogs are public to the company, or you talk to the team lead there to get access to see, you have an advantage. Ask questions on a ticket you’re interested in to see if it might be a good fit for your comfort level. If you’re in CS, bugs can be a &lt;strong&gt;great&lt;/strong&gt; first class of issue to work on because there’s a chance you’ve experienced the bug yourself or one of your customers has, which will make it even easier to reproduce. Take on one thing at a time, and ask before taking anything on. If you put up pull/merge requests, ensure the summaries can speak for themselves as you won’t be as connected to the rest of the engineers on the team for a bit. As a general rule, especially in the beginning I considered anything within 2 sprints of work (assuming your workplace’s engineering team follows a sprint model) to be a no-fly zone. &lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Get information out of your head:&lt;/strong&gt; If there are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Processes only you know, that people come to you frequently for (especially in DMs)&lt;/li&gt;
&lt;li&gt;Knowledge that you have on the product or platform that allow you to answer questions quickly&lt;/li&gt;
&lt;li&gt;Context on specific accounts that lives exclusively in your head&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Externalize these! Make use of whatever documenting tools your company has to offer and level up your teammates. This helps reduce the “risk” involved with team switches and can help ease your transition, and help take work off your plate in the meantime.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don’t lose customer context once you go:&lt;/strong&gt; Once you transition over, don’t be quick to lose your customer context — keep in touch with your CS pals, frequent the channels you were a part of before. On an engineering team, product managers are usually the people who have access to customers, and try to gauge priority based on their needs. This is something you’ll have baked in, and will be one of your greatest strengths in the beginning. With your customer context, don’t be shy about helping product managers with understanding needs and priorities of the customers you used to work with. It’ll help your entire team to have another person with customer context to bounce ideas off of.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;The imposter syndrome:&lt;/strong&gt; The imposter syndrome may feel strong while you’re trying to get your first pieces of work out there. You might not feel you deserved what you got, or that it could be taken away at any moment. Even after transitioning, it’ll be around (and may even be worse). A couple things that helped me to keep in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You’re here for a reason — you’ve worked hard to get where you are, and have the work to prove it (pull/merge requests, or old conversation threads might help on days you’re feeling it hard)&lt;/li&gt;
&lt;li&gt;Your access to customer context is one of your greatest strengths on your new engineering team, and will help everyone deliver solutions much faster&lt;/li&gt;
&lt;li&gt;You’re setting the stage for &lt;strong&gt;&lt;em&gt;other&lt;/em&gt;&lt;/strong&gt; people at your organization who want to make a similar transition&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;I hope this helps! If you’re considering a career switch like this at your organization and want to talk, shoot me an email, I’m always happy to talk and dive into things further.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a Link Shortener with Cloudflare Workers: Deployment</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Fri, 23 Jul 2021 21:11:40 +0000</pubDate>
      <link>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-deployment-23be</link>
      <guid>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-deployment-23be</guid>
      <description>&lt;p&gt;In this part of the tutorial, we'll be deploying your Worker to production, where users will be able to access it! &lt;/p&gt;

&lt;p&gt;If you haven't gone through the &lt;a href="https://dev.to/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-front-end-3pae"&gt;front-end part of this tutorial&lt;/a&gt; yet, head back to that part first.&lt;/p&gt;

&lt;p&gt;Before we continue, let's take another look at your &lt;code&gt;wrangler.toml&lt;/code&gt; file to make sure everything's in order. Your file should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"webpack"&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"redirect"&lt;/span&gt;
&lt;span class="py"&gt;account_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_ACCOUNT_ID"&lt;/span&gt;
&lt;span class="py"&gt;workers_dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="py"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;

&lt;span class="py"&gt;kv_namespaces&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SHORTEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"7654a938359f4f0e86b11afc7133166b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;preview_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"58f1a4c227534317817846d697f9ade7"&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[site]&lt;/span&gt;
&lt;span class="py"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./public"&lt;/span&gt;
&lt;span class="py"&gt;entry-point&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"workers-site"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it doesn't, please check in the back-end or front-end parts of this tutorial to ensure everything's set up properly. Once deployed, your Worker will be available for requests at &lt;code&gt;&amp;lt;name&amp;gt;.&amp;lt;yoursubdomain&amp;gt;.workers.dev&lt;/code&gt;, where &lt;code&gt;name&lt;/code&gt; is the project name you defined in the &lt;code&gt;wrangler.toml&lt;/code&gt; name key, and &lt;code&gt;yoursubdomain&lt;/code&gt; is your Workers subdomain, which you can see on your dashboard. Note that in production, your Worker will use the KV namespace available at &lt;code&gt;id&lt;/code&gt; instead of &lt;code&gt;preview_id&lt;/code&gt; automatically.&lt;/p&gt;

&lt;p&gt;If everything looks good here, deploy your Worker with &lt;code&gt;wrangler&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;wrangler publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! 🎉 Wrangler should return the URL your Worker's available at in the output, and you should be able to see it in your dashboard too. Go check it out and take it for a spin!&lt;/p&gt;

&lt;h2&gt;
  
  
  What we covered and what's next
&lt;/h2&gt;

&lt;p&gt;Thanks for coming along this tutorial journey with me! We've covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Cloudflare Workers are and some of the advantages of using them&lt;/li&gt;
&lt;li&gt;How to use Workers KV to store key:value pairs of data, and access it in your code&lt;/li&gt;
&lt;li&gt;How to use Wrangler to scaffold your project, interact with KV, test and deploy&lt;/li&gt;
&lt;li&gt;Using Workers Sites to upload and serve static assets from your KV namespace&lt;/li&gt;
&lt;li&gt;Creating API endpoints for your Worker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're looking to extend this project further, here are a few ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add basic reporting functionality&lt;/strong&gt;: Make use of your KV namespace to store the number of clicks a link has received so far!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a custom domain&lt;/strong&gt;: Make use of &lt;a href="https://developers.cloudflare.com/workers/platform/routes"&gt;routes&lt;/a&gt; to deploy the worker on a custom domain! (as it stands right now, the shortener actually lengthens domains 😛)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable the ability to delete a link&lt;/strong&gt;: Currently, links expire every 24 hours. Give users more control over when the link goes away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving error handling&lt;/strong&gt;: Currently, error handling is pretty basic. Improve it by adding a static 404 page when a slug isn't found/expired, or experiment with piping failures to a 3rd party log ingestion service!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know if you add in any of these features or build whole new things on top of this. Hope you enjoyed the tutorial; have fun building with Cloudflare Workers!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Build a Link Shortener with Cloudflare Workers: The front-end</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Fri, 23 Jul 2021 21:11:10 +0000</pubDate>
      <link>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-front-end-3pae</link>
      <guid>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-front-end-3pae</guid>
      <description>&lt;p&gt;In this part of the tutorial, we'll build a front-end atop our back-end to let users input a long link and get a short link back. Our front-end will be a simple Vue application that just interfaces with our &lt;code&gt;POST /links&lt;/code&gt; endpoint, and will be served up using &lt;a href="https://developers.cloudflare.com/workers/platform/sites" rel="noopener noreferrer"&gt;Workers Sites&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you haven't gone through the &lt;a href="https://dev.to/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-back-end-35bd"&gt;back-end&lt;/a&gt; part of this tutorial yet to set up the routes this front-end depends on, head back to that part first!&lt;/p&gt;

&lt;h2&gt;
  
  
  How Workers Sites works
&lt;/h2&gt;

&lt;p&gt;In the back-end part of this tutorial, we used &lt;a href="https://developers.cloudflare.com/workers/learning/how-kv-works" rel="noopener noreferrer"&gt;Workers KV&lt;/a&gt; to store key-value pairs of slugs for long URLs. What Workers Sites allows you to do is automatically upload your site's static content to a KV namespace as well, where it can be retrieved and displayed by your Worker. &lt;/p&gt;

&lt;p&gt;These assets are stored in another namespace that Wrangler can create for you, and your Worker can retrieve using the &lt;a href="https://github.com/cloudflare/kv-asset-handler" rel="noopener noreferrer"&gt;kv-asset-handler&lt;/a&gt; package. In our Worker code, we can grab the correct asset depending on the request it receives. &lt;/p&gt;

&lt;p&gt;To get started, in your project directory, install the kv-asset-handler package: &lt;code&gt;npm i @cloudflare/kv-asset-handler&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A bit of home renovation first
&lt;/h2&gt;

&lt;p&gt;To make this work, we'll need to re-structure our project folder a bit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Move the &lt;code&gt;index.js&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; from the project root into their own folder, which we'll call &lt;code&gt;workers-site&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a &lt;code&gt;public&lt;/code&gt; directory in your project root, with a &lt;code&gt;static&lt;/code&gt; subdirectory in it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modify your &lt;code&gt;wrangler.toml&lt;/code&gt; file to add this section at the bottom:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[site]&lt;/span&gt;
&lt;span class="py"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./public"&lt;/span&gt;
&lt;span class="py"&gt;entry-point&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"workers-site"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going forward, Wrangler will now upload your static assets in &lt;code&gt;public&lt;/code&gt; to their own KV namespace.&lt;/p&gt;

&lt;p&gt;At the end of these steps, your folder structure should look something like this (assuming the project root is called &lt;code&gt;redirect&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redirect
| wrangler.toml
└───public
    └───static
└───workers-site
    └───index.js
    └───package.json
    └───package-lock.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Vue application
&lt;/h2&gt;

&lt;p&gt;First, copy the stylesheet from the &lt;a href="https://github.com/mm/cf-shortener/blob/main/public/static/style.css" rel="noopener noreferrer"&gt;finished project&lt;/a&gt; into your &lt;code&gt;public/static&lt;/code&gt; directory. &lt;/p&gt;

&lt;p&gt;Afterwards, copy the &lt;code&gt;index.html&lt;/code&gt; file from the &lt;a href="https://github.com/mm/cf-shortener/blob/main/public/index.html" rel="noopener noreferrer"&gt;finished project&lt;/a&gt; directly into the &lt;code&gt;public&lt;/code&gt; folder. This tutorial won't focus on the specifics of Vue too much, but let's explore what the code is doing. Looking at this section of the code (line 16-32):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit.prevent=&lt;/span&gt;&lt;span class="s"&gt;"handleUrlSubmit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"input-url"&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;
    &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"40"&lt;/span&gt;
    &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"https://google.com"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
    &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"longUrl"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"input-submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Shorten"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"shortUrl"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Your new shortened URL is:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ shortUrl }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we've created a &lt;a href="https://v3.vuejs.org/guide/forms.html#form-input-bindings" rel="noopener noreferrer"&gt;data binding&lt;/a&gt; on our form inputs using the &lt;code&gt;v-model&lt;/code&gt; directive. Whenever the input box for the URL is updated, the &lt;code&gt;longUrl&lt;/code&gt; data property will be updated.&lt;/p&gt;

&lt;p&gt;We register an event listener for the &lt;code&gt;submit&lt;/code&gt; event on this form. The &lt;code&gt;handleUrlSubmit&lt;/code&gt; method we define will take care of interacting with the endpoint we defined before, and will update the &lt;code&gt;shortUrl&lt;/code&gt; data property. Once this is available, the URL will be displayed to the user (visibility toggled by the &lt;code&gt;v-if&lt;/code&gt; directive).&lt;/p&gt;

&lt;p&gt;Taking a look at the &lt;code&gt;handleUrlSubmit&lt;/code&gt; method on the Vue app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;handleUrlSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/links&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Issue saving URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortened&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we're doing a &lt;code&gt;fetch&lt;/code&gt; request (with very little error handling) to our &lt;code&gt;/links&lt;/code&gt; endpoint, and retrieving the &lt;code&gt;shortened&lt;/code&gt; value from the API response. The &lt;code&gt;shortUrl&lt;/code&gt; data property gets set to this value, and the shortened URL is displayed to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;kv-asset-handler&lt;/code&gt; to render our application
&lt;/h2&gt;

&lt;p&gt;There are two scenarios where we'd want to render static assets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user visits the homepage (i.e. the path is &lt;code&gt;/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A static asset is requested (e.g &lt;code&gt;/static/style.css&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To intercept these requests, while still responding to requests to our API endpoints, we can define a middleware function. This would either pass the fetch event to the router or &lt;code&gt;kv-asset-handler&lt;/code&gt;'s &lt;code&gt;getAssetFromKV&lt;/code&gt; function, which consumes a &lt;code&gt;FetchEvent&lt;/code&gt; and returns a &lt;code&gt;Response&lt;/code&gt; based on what's stored in the KV namespace for static assets.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;index.js&lt;/code&gt;. First, import the &lt;code&gt;getAssetFromKV&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAssetFromKV&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cloudflare/kv-asset-handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, let's write our function to pass the event/request around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;requestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;requestUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAssetFromKV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that our route handlers currently take in a &lt;code&gt;Request&lt;/code&gt; object, while the &lt;code&gt;getAssetFromKV&lt;/code&gt; function takes in the whole &lt;code&gt;FetchEvent&lt;/code&gt;. Next, let's hook into this function on our fetch event listener. Modify the listener from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these changes made, it's time to take the Worker for a test spin! Run &lt;code&gt;wrangler dev&lt;/code&gt;. Notice you'll get a bit of extra output as your static assets get populated into their own KV namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;wrangler dev

🌀  Using namespace &lt;span class="k"&gt;for &lt;/span&gt;Workers Site &lt;span class="s2"&gt;"__redirect-workers_sites_assets_preview"&lt;/span&gt;
✨  Success
👂  Listening on http://127.0.0.1:8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, you should be able to see it in action:&lt;/p&gt;

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

&lt;p&gt;Note your URL might look a little different. If you now take this key and append it to the URL in your address bar (e.g &lt;code&gt;127.0.0.1:8787/nFXAau&lt;/code&gt; for me), it should redirect! Our routing has been set up properly.&lt;/p&gt;

&lt;p&gt;If you peek at your KV namespace for the static assets in your dashboard, you should see them listed:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  🎉 The front-end is ready to rock!
&lt;/h2&gt;

&lt;p&gt;The front-end is ready to go and now it's time to deploy our application with Wrangler. In the next part of this tutorial we'll deploy the link shortener -- we're almost there!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mmascioni/build-a-link-shortener-with-cloudflare-workers-deployment-23be"&gt;➡️ Onward to deploying the application!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Build a Link Shortener with Cloudflare Workers: The back-end</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Fri, 23 Jul 2021 21:10:33 +0000</pubDate>
      <link>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-back-end-35bd</link>
      <guid>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-back-end-35bd</guid>
      <description>&lt;p&gt;In this part of the tutorial, we'll use Cloudflare Workers to accomplish two things and build the back-end of our app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a new short link and returning the slug of that short link (e.g given a link like &lt;code&gt;shorturl.at/gIUX4&lt;/code&gt;, the slug is &lt;code&gt;gIUX4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Redirecting from a short link to the full link, given a slug&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before we begin
&lt;/h2&gt;

&lt;p&gt;If you haven't gone through the &lt;a href="https://dev.to/mmascioni/build-a-link-shortener-with-cloudflare-workers-1j3i"&gt;introduction to the project&lt;/a&gt; part of this tutorial yet to set up your environment, head back to that part first!&lt;/p&gt;

&lt;p&gt;Let's install two packages this project will depend on. We'll make use of these two very lightweight packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/kwhitley/itty-router" rel="noopener noreferrer"&gt;itty-router&lt;/a&gt;: Will allow us to declare URL routes and parameters in those routes easily&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ai/nanoid" rel="noopener noreferrer"&gt;nanoid&lt;/a&gt;: What we'll use to generate the random slugs to access the URLs at&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switch to the project directory we created in the last part of this tutorial, and use &lt;code&gt;npm&lt;/code&gt; to install &lt;code&gt;itty-router&lt;/code&gt; and &lt;code&gt;nanoid&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i itty-router nanoid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, please check to ensure that you've populated your Account ID in your &lt;code&gt;wrangler.toml&lt;/code&gt; file, and have set the &lt;code&gt;type&lt;/code&gt; for your project to &lt;code&gt;webpack&lt;/code&gt; to package up these dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up our Workers KV Store
&lt;/h2&gt;

&lt;p&gt;To store slugs and the URLs they point to, we'll use &lt;a href="https://developers.cloudflare.com/workers/learning/how-kv-works" rel="noopener noreferrer"&gt;Workers KV&lt;/a&gt;. It's optimized for high-read applications where we'll need to access these keys frequently, and can be easily accessed from your Worker code. Each key will be a slug (e.g &lt;code&gt;gIUX4&lt;/code&gt;), and the value the URL they point to (e.g &lt;code&gt;https://www.youtube.com/watch?v=dQw4w9WgXcQ&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;KV stores are organized in &lt;em&gt;namespaces&lt;/em&gt;, and you can create them using Wrangler. Let's create one called &lt;code&gt;SHORTEN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;wrangler kv:namespace create &lt;span class="s2"&gt;"SHORTEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get some output back from Wrangler on what to add to your &lt;code&gt;wrangler.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;🌀  Creating namespace with title &lt;span class="s2"&gt;"rd-test-SHORTEN"&lt;/span&gt;
✨  Success!
Add the following to your configuration file:
kv_namespaces &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; 
         &lt;span class="o"&gt;{&lt;/span&gt; binding &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SHORTEN"&lt;/span&gt;, &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"48ae98ff404a460a87d0348bb5197595"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, I'd add that &lt;code&gt;kv_namespaces&lt;/code&gt; entry to the end of my &lt;code&gt;wrangler.toml&lt;/code&gt; file. The &lt;code&gt;binding&lt;/code&gt; key here lets you access the KV namespace by the &lt;code&gt;SHORTEN&lt;/code&gt; variable in your Worker code. If you log in to the Cloudflare Workers dashboard, you should be able to see your namespace listed under KV too:&lt;/p&gt;

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

&lt;p&gt;Clicking "View" lets you see all associated keys and values, though it should be empty now. In your Worker's code, you can now interface with this namespace through the &lt;code&gt;SHORTEN&lt;/code&gt; variable, which we'll see in a moment.&lt;/p&gt;

&lt;p&gt;Finally, create a &lt;em&gt;preview&lt;/em&gt; namespace. This will be automatically used in development (i.e. when running &lt;code&gt;wrangler dev&lt;/code&gt;) when previewing your Worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;wrangler kv:namespace create &lt;span class="s2"&gt;"SHORTEN"&lt;/span&gt; &lt;span class="nt"&gt;--preview&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the &lt;code&gt;preview_id&lt;/code&gt; provided in the output of this command and add it to the &lt;code&gt;wrangler.toml&lt;/code&gt; file, in the same entry as your &lt;code&gt;SHORTEN&lt;/code&gt; KV namespace. For example, if my output was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;🌀  Creating namespace with title &lt;span class="s2"&gt;"rd-test-SHORTEN_preview"&lt;/span&gt;
✨  Success!
Add the following to your configuration file:
kv_namespaces &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; 
         &lt;span class="o"&gt;{&lt;/span&gt; binding &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SHORTEN"&lt;/span&gt;, preview_id &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"d7044b5c3dde494a9baf1d3803921f1c"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then my &lt;code&gt;wrangler.toml&lt;/code&gt; file would now have this under &lt;code&gt;kv_namespaces&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;kv_namespaces&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SHORTEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"48ae98ff404a460a87d0348bb5197595"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;preview_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"d7044b5c3dde494a9baf1d3803921f1c"&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you check the Cloudflare Workers KV section of the dashboard, you should now see two namespaces, one appended with &lt;code&gt;_preview&lt;/code&gt; and one not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating slugs and creating short links
&lt;/h2&gt;

&lt;p&gt;We'll define an API endpoint, &lt;code&gt;POST /links&lt;/code&gt;, that takes in a JSON payload like &lt;code&gt;{ "url": "https://google.com" }&lt;/code&gt;, then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates a random, alphanumeric slug using &lt;code&gt;nanoid&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Saves a new entry to the &lt;code&gt;SHORTEN&lt;/code&gt; KV namespace with the key as the slug in (1) and the value as the URL in the request payload&lt;/li&gt;
&lt;li&gt;Returns the slug, and the short URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To do this, open up &lt;code&gt;index.js&lt;/code&gt; in your project folder. Let's import some functions we'll need from &lt;code&gt;itty-router&lt;/code&gt; and &lt;code&gt;nanoid&lt;/code&gt;, create a router, and set up a custom alphabet to pick our URL slugs from (the &lt;code&gt;6&lt;/code&gt; ensures they're 6 characters):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;itty-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customAlphabet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nanoid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nanoid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;customAlphabet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's define our API endpoint and walk through what it's doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/links&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nanoid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add slug to our KV store so it can be retrieved later:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SHORTEN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expirationTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;shortenedURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;responseBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Link shortened successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;shortened&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shortenedURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Must provide a valid URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we're registering a route at &lt;code&gt;/links&lt;/code&gt; to accept POST requests, and consuming the &lt;code&gt;request&lt;/code&gt; object passed from our fetch event the worker listens for. We're using our custom alphabet &lt;code&gt;nanoid&lt;/code&gt; to generate a 6-character random slug, and then pulling the URL out of the request body. We also add this to our KV namespace we generated earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SHORTEN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expirationTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;SHORTEN&lt;/code&gt; variable is bound to your Worker after adding it in &lt;code&gt;wrangler.toml&lt;/code&gt;. Here we're setting the key to automatically expire in a day, but you can choose to set this to any value you want (or not make it expire at all!) If all goes well, we'll return the slug and the full shortened URL so the front-end can make use of it.&lt;/p&gt;

&lt;p&gt;Go to this part of &lt;code&gt;index.js&lt;/code&gt; that came with the template Wrangler used to create the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fetch event is what's received once an HTTP request is made to your Worker. We'll modify this handler to connect to your &lt;code&gt;router&lt;/code&gt; function instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, your router will be passed the &lt;code&gt;Request&lt;/code&gt; object in the fetch event and can act on it, passing the request off to the correct route (like the one we defined above). Let's test it out! Run &lt;code&gt;wrangler dev&lt;/code&gt; and make a test request to your Worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:8787/links"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json; charset=utf-8'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;$'{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}'&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Link shortened successfully"&lt;/span&gt;,&lt;span class="s2"&gt;"slug"&lt;/span&gt;:&lt;span class="s2"&gt;"TCqff7"&lt;/span&gt;,&lt;span class="s2"&gt;"shortened"&lt;/span&gt;:&lt;span class="s2"&gt;"https://redirect.mascioni.workers.dev/TCqff7"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the value in &lt;code&gt;shortened&lt;/code&gt; depends on your Workers subdomain and project name you chose in the beginning. If you head over to your &lt;code&gt;SHORTEN_preview&lt;/code&gt; KV namespace in the Workers dashboard, you should see the entry was added too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2y7inmzahyqzhr1gfda.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2y7inmzahyqzhr1gfda.png" alt="The TCqff7 key now has a value for a link to everyone's favourite video"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;You can also remove the &lt;code&gt;handleRequest&lt;/code&gt; function the template comes with from &lt;code&gt;index.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirecting from short links to full links
&lt;/h2&gt;

&lt;p&gt;The implementation for this is similar to the one for generating the short link, except this time we're calling &lt;code&gt;.get&lt;/code&gt; on our KV namespace and returning a redirect. &lt;/p&gt;

&lt;p&gt;Let's register another route on our router, in &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SHORTEN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Key not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, we're registering a &lt;code&gt;GET /:slug&lt;/code&gt; route, and making use of the &lt;code&gt;slug&lt;/code&gt; parameter to fetch a value from our KV namespace. If a value exists, we'll return it with a &lt;code&gt;301&lt;/code&gt; status code and set the &lt;code&gt;Location&lt;/code&gt; header appropriately to do the redirect, otherwise we'll throw a &lt;code&gt;404&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;wrangler dev&lt;/code&gt; isn't running anymore, run it again, and now make a &lt;code&gt;GET&lt;/code&gt; request from your browser to try and retrieve the URL you stored earlier! In my case, my Worker is listening for requests on port &lt;code&gt;8787&lt;/code&gt;, and I saved a URL with slug &lt;code&gt;TCqff7&lt;/code&gt;, so I'd go to &lt;code&gt;http://127.0.0.1:8787/TCqff7&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 Back-end is finished!
&lt;/h2&gt;

&lt;p&gt;With that, the back-end of our app is basically done! We can now generate short URLs and redirect from them easily. In the next part of this tutorial, we'll use &lt;a href="https://developers.cloudflare.com/workers/platform/sites" rel="noopener noreferrer"&gt;Workers Sites&lt;/a&gt; to build a front-end on top of this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-front-end-3pae"&gt;➡️ Onward to building the front-end!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Build a Link Shortener with Cloudflare Workers</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Fri, 23 Jul 2021 21:10:13 +0000</pubDate>
      <link>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-1j3i</link>
      <guid>https://forem.com/mmascioni/build-a-link-shortener-with-cloudflare-workers-1j3i</guid>
      <description>&lt;p&gt;&lt;a href="https://workers.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt; is a serverless platform that allows you to run back-end code in response to an event (like an HTTP request). Similar to other serverless platforms, this means that you won't have to keep a server running to wait for requests, allowing you to save money by only paying for resources you need.&lt;/p&gt;

&lt;p&gt;Workers run on &lt;a href="https://www.cloudflare.com/network/" rel="noopener noreferrer"&gt;Cloudflare's Edge Network&lt;/a&gt; and are incredibly fast, with a very generous free tier. In this 4-part tutorial series, we'll build one to power a link shortener that looks something like this:&lt;/p&gt;

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

&lt;p&gt;The finished product is also available on &lt;a href="https://github.com/mm/cf-shortener" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you want to dive in and use the code for your own projects! For this project, we'll be using JavaScript, but Workers &lt;a href="https://developers.cloudflare.com/workers/platform/languages" rel="noopener noreferrer"&gt;support other languages too&lt;/a&gt; (e.g Rust)&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt; account. The project we're building should fall within the Workers Free plan.&lt;/li&gt;
&lt;li&gt;Wrangler (the CLI for working with Workers) installed: e.g with NPM, &lt;code&gt;npm i @cloudflare/wrangler -g&lt;/code&gt;: see &lt;a href="https://github.com/cloudflare/wrangler" rel="noopener noreferrer"&gt;install instructions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we'll be building
&lt;/h2&gt;

&lt;p&gt;Our link shortener has two main pieces that we'll build out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A back-end that should be able to take a "long" URL, generate a "short" one, and return the shortened URL. It should also be able to return a redirect to the correct long URL, given a short URL.&lt;/li&gt;
&lt;li&gt;A front-end to interact with the link-shortening back-end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For (1), we can create a Worker to listen for HTTP requests and route accordingly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /links&lt;/code&gt;: Generate a new short URL from a long one, returning a short &lt;code&gt;slug&lt;/code&gt; to access it at&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /:slug&lt;/code&gt;: Looks for a long URL with this &lt;code&gt;slug&lt;/code&gt;, and redirects to it if it exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an application like this, the &lt;a href="https://redis.com" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; in-memory database may be an awesome choice, as we could store the slugs as keys and quickly access a long URL by slug. One huge benefit with Cloudflare Workers is that a &lt;a href="https://www.cloudflare.com/products/workers-kv/" rel="noopener noreferrer"&gt;key-value store, Workers KV&lt;/a&gt; is built in and easily accessible from the Worker function. We'll be using Workers KV here.&lt;/p&gt;

&lt;p&gt;For (2), our Worker can also serve static assets, and we'll store our HTML/CSS files using Workers KV too via &lt;a href="https://developers.cloudflare.com/workers/platform/sites" rel="noopener noreferrer"&gt;Workers Sites&lt;/a&gt;. For fun, the front-end will use Vue too. More on this soon!&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ensure you've installed &lt;a href="https://github.com/cloudflare/wrangler" rel="noopener noreferrer"&gt;Wrangler&lt;/a&gt; as described above. Afterwards, run &lt;code&gt;wrangler login&lt;/code&gt; and you'll be prompted to sign-in to your Cloudflare account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generate a new project using a JavaScript template, since that's what we'll be using for this tutorial:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler generate &amp;lt;project-name&amp;gt; 
https://github.com/cloudflare/worker-template
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This will create a new folder at &lt;code&gt;&amp;lt;project-name&amp;gt;&lt;/code&gt;. Open up &lt;code&gt;wrangler.toml&lt;/code&gt; in that folder, and set &lt;code&gt;account_id&lt;/code&gt; to the account ID the Wrangler CLI returns. Also, set &lt;code&gt;type = webpack&lt;/code&gt; instead of &lt;code&gt;javascript&lt;/code&gt;, to package some dependencies we'll be installing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your Worker's code is in &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * Respond with hello worker text
 * @param {Request} request
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello worker!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening here? When an HTTP request hits Cloudflare's edge network, and there's a Worker mapped to the route that got accessed, a &lt;code&gt;FetchEvent&lt;/code&gt; object will get passed to the &lt;code&gt;fetch&lt;/code&gt; event listener. The &lt;code&gt;FetchEvent&lt;/code&gt; object has a &lt;code&gt;respondWith&lt;/code&gt; method that lets us return a response right away. The Worker uses this to return a &lt;code&gt;Response&lt;/code&gt; object with the &lt;code&gt;Hello worker!&lt;/code&gt; text. For other things you can do with the &lt;code&gt;FetchEvent&lt;/code&gt; object, check out the &lt;a href="https://developers.cloudflare.com/workers/learning/fetch-event-lifecycle" rel="noopener noreferrer"&gt;FetchEvent lifecycle&lt;/a&gt; docs.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;wrangler dev&lt;/code&gt; from your project directory. Behind the scenes, this &lt;a href="https://developers.cloudflare.com/workers/cli-wrangler/commands#dev" rel="noopener noreferrer"&gt;creates a tunnel&lt;/a&gt; between your machine and the edge server that handles your requests. &lt;/p&gt;

&lt;p&gt;You should get a URL to try sending a request to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;💁  watching &lt;span class="s2"&gt;"./"&lt;/span&gt;
👂  Listening on http://127.0.0.1:8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make a request to that URL. You should see &lt;code&gt;Hello worker!&lt;/code&gt; returned. If all is working so far, it's time to dive into building the back-end!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mmascioni/build-a-link-shortener-with-cloudflare-workers-the-back-end-35bd"&gt;➡️ Onward to building the back-end&lt;/a&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Testing and debugging webhooks with ngrok</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Sun, 07 Feb 2021 20:51:52 +0000</pubDate>
      <link>https://forem.com/mmascioni/testing-and-debugging-webhooks-with-ngrok-4alp</link>
      <guid>https://forem.com/mmascioni/testing-and-debugging-webhooks-with-ngrok-4alp</guid>
      <description>&lt;p&gt;Let's say you're building a web application that receives webhooks from external services like &lt;a href="https://docs.github.com/en/developers/webhooks-and-events/about-webhooks" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; or others. These webhooks work by sending HTTP POST requests to an endpoint on your server, allowing you to listen for events as they happen without having to continuously poll for new data. If you're developing locally though, you won't be able to receive webhooks at &lt;code&gt;http://localhost:8000&lt;/code&gt; (or any other local URL!) This is where &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; comes in, allowing you to create a temporary public URL to tunnel HTTP traffic to your local application.&lt;/p&gt;

&lt;p&gt;In this tutorial I'll walk through installing ngrok and using it to test out the &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; webhook, but this can work for any webhook you need to work with!&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing ngrok
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Head over to &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; and sign up for an account. You don't really need one for this tutorial so you can skip this step if you want, but having an account gives you a few extra features (like longer session times)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On macOS, ngrok can be installed via Homebrew: &lt;code&gt;brew install --cask ngrok&lt;/code&gt;. You can also download an executable for macOS, Windows or Linux &lt;a href="https://ngrok.com/download" rel="noopener noreferrer"&gt;directly from their website&lt;/a&gt;. Keep track of where it's been downloaded so you'll know which path to access it at after. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Try running it from a terminal window to make sure it's working: &lt;code&gt;ngrok help&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The syntax for tunneling HTTP traffic to a port on your local computer is &lt;code&gt;ngrok http &amp;lt;port_to_tunnel_to&amp;gt;&lt;/code&gt;. For example, if I wanted to tunnel HTTP requests to a server listening for requests at port 5000 on my local machine, I would type &lt;code&gt;ngrok http 5000&lt;/code&gt; in a shell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fo9pyculgbd3v3bm5cy78.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fo9pyculgbd3v3bm5cy78.gif" alt="Typing ngrok http 5000 in my terminal to start a tunnel to port 5000 on my machine" width="570" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll notice the &lt;code&gt;Forwarding: https://11d51c640b97.ngrok.io -&amp;gt; http://localhost:5000&lt;/code&gt;. This means all requests to &lt;code&gt;https://11d51c640b97.ngrok.io&lt;/code&gt; will be forwarded to port 5000 on my local machine, where my application can receive them! You can also access a request inspector in a browser at &lt;code&gt;http://127.0.0.1:4040&lt;/code&gt;, where you can see (and even replay) any HTTP requests that went through. Press Ctrl+C whenever you're done and the tunnel will be closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using ngrok to test the Stripe webhook
&lt;/h2&gt;

&lt;p&gt;In this example I'm going to use a Flask application that's listening for incoming webhooks from Stripe at &lt;code&gt;/webhooks/stripe&lt;/code&gt;. Let's say I'm interested in &lt;code&gt;charge.succeeded&lt;/code&gt; events, because these tell me someone has paid for something on my fictitious platform! If you want to follow along with this tutorial, all the code is available on GitHub. Set up your local environment and install a few dependencies (ensure Pipenv is installed first):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mm/stripe-ngrok-example.git
&lt;span class="nb"&gt;cd &lt;/span&gt;stripe-ngrok-example
pipenv &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a look at the code in &lt;code&gt;app.py&lt;/code&gt;, which defines the endpoint that's listening for events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receive_stripe_webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Receives a webhook payload from Stripe.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Try to parse a webhook payload, get upset if we couldn't
&lt;/span&gt;    &lt;span class="c1"&gt;# parse any JSON in the body:
&lt;/span&gt;    &lt;span class="n"&gt;stripe_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stripe_payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Could not parse webhook payload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe_payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Could not determine event type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;charge.succeeded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Pull fields out of payload:
&lt;/span&gt;        &lt;span class="n"&gt;data_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe_payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Here we just log the transaction, but at this point we can do
&lt;/span&gt;        &lt;span class="c1"&gt;# anything! (Provision accounts, push to a database, etc.)
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Customer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; made a purchase of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; cents!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Webhook received&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides a bit of input data handling, all this code is doing is waiting for a &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;/webhooks/stripe&lt;/code&gt;, checking for a &lt;code&gt;charge.succeeded&lt;/code&gt; event (we successfully charged someone on our platform), and logging a bit of information about the charge to the console. You can start a local Flask server by running &lt;code&gt;pipenv run flask run&lt;/code&gt; in a shell from the project directory. The server will let you know where it's listening for new requests -- in my case, &lt;code&gt;http://127.0.0.1:5000/&lt;/code&gt;. Let's expose this via ngrok and have Stripe send us some data!&lt;/p&gt;

&lt;p&gt;While the server's running, in a new terminal window type &lt;code&gt;ngrok http 5000&lt;/code&gt; (or whatever port the Flask app is listening at):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcvu1hhfkykp2t0of3s4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcvu1hhfkykp2t0of3s4v.png" alt="After starting ngrok, I have access to a forwarding URL -- https://07136e03dd33.ngrok.io" width="752" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the forwarding URL generated by ngrok. In my case, mine is &lt;code&gt;https://07136e03dd33.ngrok.io&lt;/code&gt;. Now, let's have Stripe send data here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Log in to your Stripe account (here I'm using a test account)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the left sidebar, click on "Developers", then "Webhooks".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under "Endpoints", click "+ Add Endpoint". The endpoint URL is the forwarding URL ngrok gave you (plus whatever path your server is listening at). In my case, it's &lt;code&gt;https://07136e03dd33.ngrok.io/webhooks/stripe&lt;/code&gt; (this would be equivalent to &lt;code&gt;http://localhost:5000/webhooks/stripe&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx51bshz9woie2qq2s01l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx51bshz9woie2qq2s01l.png" alt="Adding my webhook endpoint in the Stripe console" width="601" height="684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here I set it up so I only receive &lt;code&gt;charge.succeeded&lt;/code&gt; events, but there's all kinds you can listen for in the &lt;a href="https://stripe.com/docs/api/events" rel="noopener noreferrer"&gt;Stripe docs&lt;/a&gt; (my favourite docs ever)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, record a charge for a customer. I'm in a test environment, so I'll just record a charge for a test customer named after me:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9z4va2q5auqf3xw719bp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9z4va2q5auqf3xw719bp.png" alt="Recording a payment" width="472" height="494"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you've been checking your Flask server, you'll notice we received a request to our endpoint in the logs:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Customer cus_Itm097l6URXxaZ made a purchase of 400 cents!
127.0.0.1 - - &lt;span class="o"&gt;[&lt;/span&gt;06/Feb/2021 16:39:42] &lt;span class="s2"&gt;"POST /webhooks/stripe HTTP/1.1"&lt;/span&gt; 200 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Head over to &lt;code&gt;http://localhost:4040&lt;/code&gt; in your browser (or whatever URL ngrok gave you for your "Web Interface") Here, you'll see a log of the request made to your local app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqhgkf58av7yiul52smal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqhgkf58av7yiul52smal.png" alt="Seeing the request log from ngrok in my browser" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let's say you make some changes to your code and want to test the same payload against the new code. You don't need to record another payment in Stripe! You can hit the "Replay" button in the ngrok inspector and replay the request (or even modify it before replaying). This can help you test code faster without adding huge amounts of data to your test environment (be it Stripe or any other)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hit Ctrl+C on both your ngrok session and Flask server once you want to stop receiving requests. That's all there is to it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to tunnel HTTP requests to your local server to test webhooks with ngrok&lt;/li&gt;
&lt;li&gt;Using the ngrok inspector to inspect and replay requests to your server without needing to generate more events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know if it was helpful in testing your own applications. Here we covered testing the Stripe webhook this way, but this strategy can work for other webhooks too!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Header photo by &lt;a href="https://unsplash.com/@sydneylens?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Paul Carmona&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/spiderweb?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What Should We Play? - A DO Hackathon Submission</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Sat, 26 Dec 2020 21:08:09 +0000</pubDate>
      <link>https://forem.com/mmascioni/what-should-we-play-a-do-hackathon-submission-36k1</link>
      <guid>https://forem.com/mmascioni/what-should-we-play-a-do-hackathon-submission-36k1</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;I built a searchable index of online games people can play with each other (or on their own!) to feel connected while social distancing measures are in place. It can also quickly pick a game for you depending on how many people are in your online party, and anyone can suggest new games to help improve the index for others!&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;Program for the People&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://whatshouldweplay.xyz" rel="noopener noreferrer"&gt;https://whatshouldweplay.xyz&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqrzke39iai15cpu3dowq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqrzke39iai15cpu3dowq.png" alt="Main page with a listing of all games"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkwree8503z2muev6eppl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkwree8503z2muev6eppl.png" alt="Main page, but in dark mode"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fktpartt4v9mn3h2wu2eo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fktpartt4v9mn3h2wu2eo.png" alt="Having the website pick a game for you"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm731pluna3ip71ze1kpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm731pluna3ip71ze1kpc.png" alt="Filling out a form to suggest a new game get added"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;This is a little website I built to showcase games you could play with friends online while on a Zoom/Meet/FaceTime/some other call together. The site breaks down games based on how many players can fit in a game and whether they're free or not. Besides searching, anyone can quickly have the website generate game suggestions for them depending on their party size! The site also features a suggestion form so that anyone can help add new games to the index. This submission is broken down into the back-end (main repository), front-end as well as an admin panel to quickly review and add in new suggestions captured on the form. &lt;/p&gt;

&lt;p&gt;The entire project was written using Python for the backend and React in the frontend, with Auth0 managing authentication for the admin panel.&lt;/p&gt;

&lt;p&gt;For those trying to use this data/functionality in their own applications, I've also exposed certain endpoints on the app's REST API &lt;a href="https://github.com/mm/wswp#-using-the-api" rel="noopener noreferrer"&gt;that are documented here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;The back-end, front-end and admin panel code live in separate repositories. Each have a &lt;strong&gt;Deploy to DigitalOcean&lt;/strong&gt; button to easily get them live on App Platform or a Docker Compose file to get up and running on your local machine!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Main repository and back-end: &lt;a href="https://github.com/mm/wswp" rel="noopener noreferrer"&gt;https://github.com/mm/wswp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Front-end: &lt;a href="https://github.com/mm/wswp-frontend" rel="noopener noreferrer"&gt;https://github.com/mm/wswp-frontend&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Admin panel: &lt;a href="https://github.com/mm/wswp-admin" rel="noopener noreferrer"&gt;https://github.com/mm/wswp-admin&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;All repositories are released under the MIT License:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Back-end: &lt;a href="https://github.com/mm/wswp/blob/main/LICENSE" rel="noopener noreferrer"&gt;https://github.com/mm/wswp/blob/main/LICENSE&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Front-end: &lt;a href="https://github.com/mm/wswp-frontend/blob/main/LICENSE" rel="noopener noreferrer"&gt;https://github.com/mm/wswp-frontend/blob/main/LICENSE&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Admin: &lt;a href="https://github.com/mm/wswp-admin/blob/main/LICENSE" rel="noopener noreferrer"&gt;https://github.com/mm/wswp-admin/blob/main/LICENSE&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Throughout the lockdown, a constant source of entertainment has been playing games with friends or co-workers. "What should we play?" ended up getting asked a &lt;em&gt;lot&lt;/em&gt; on those calls, and I saw a lot of threads on Reddit with great games to play and news articles but no website dedicated to it, so this was a cool opportunity to try building one!&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it
&lt;/h3&gt;

&lt;p&gt;The tech I used in this project was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Back-end:&lt;/strong&gt; Python (Flask, SQLAlchemy, Marshmallow), PostgreSQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Front-end:&lt;/strong&gt; React, Chakra UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth/Identity:&lt;/strong&gt; Auth0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I love working on back-end projects and APIs, but I hadn't dabbled in front-end work too much. I really wanted to learn more about building React apps, so this was a project that allowed me to dive into React in general and learn how to use &lt;a href="https://chakra-ui.com" rel="noopener noreferrer"&gt;Chakra UI&lt;/a&gt;, an amazing component library that helped me make this whole thing a reality. Their documentation is &lt;em&gt;stellar&lt;/em&gt;, and I have a whole new level of appreciation for how tricky UI work can be. Also, building an admin panel and submission functionality gave me an excuse to try out authentication with JWTs and &lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; as my identity platform. Their Flask and React documentation helped me get up and running quickly.&lt;/p&gt;

&lt;p&gt;DigitalOcean's App Platform powered this project the whole way through. I couldn't believe how intuitive it was to get my API + database set up, and &lt;em&gt;then&lt;/em&gt; add a static site atop it with environment variables set dynamically depending on the app's base URL or database URL. I deployed the admin panel as a separate static site to not complicate the main build too much. Some things I really appreciated about App Platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easily adding a static site to a project that already has a service &amp;amp; database defined&lt;/li&gt;
&lt;li&gt;Environment variables super easy to manage&lt;/li&gt;
&lt;li&gt;Built-in console a huge plus for running CLI tasks on the fly (like running database migrations)&lt;/li&gt;
&lt;li&gt;Painless domain management and component HTTP routing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm hoping to use this project to write a tutorial about deploying Python/Flask + React apps to App Platform in the future!&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Component library powering the front-end (and the awesome dark mode): &lt;a href="https://chakra-ui.com" rel="noopener noreferrer"&gt;Chakra UI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Icons courtesy of &lt;a href="https://primer.style/octicons/" rel="noopener noreferrer"&gt;Octicons&lt;/a&gt;, &lt;a href="https://heroicons.com" rel="noopener noreferrer"&gt;Hero Icons&lt;/a&gt;, and &lt;a href="https://fontawesome.com" rel="noopener noreferrer"&gt;Font Awesome&lt;/a&gt; via &lt;a href="https://react-icons.github.io/react-icons/" rel="noopener noreferrer"&gt;react-icons&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;If you see something that could be improved on the site, please let me know or open up an issue on GitHub! I had so much fun developing this and would love to build it into something the community could get use out of.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dohackathon</category>
      <category>python</category>
      <category>react</category>
    </item>
    <item>
      <title>Using external Python packages with AWS Lambda layers</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Sun, 06 Dec 2020 17:56:00 +0000</pubDate>
      <link>https://forem.com/mmascioni/using-external-python-packages-with-aws-lambda-layers-526o</link>
      <guid>https://forem.com/mmascioni/using-external-python-packages-with-aws-lambda-layers-526o</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/welcome.html" rel="noopener noreferrer"&gt;Amazon Lambda&lt;/a&gt; is an awesome service that lets you run code serverlessly: rather than keeping a server running to execute your code anytime, a server is spun up only when necessary in response to some event. This event could be from an HTTP request, a message posted to a queueing service like &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html" rel="noopener noreferrer"&gt;Amazon SQS&lt;/a&gt;, or even when a file is uploaded to an S3 bucket! &lt;/p&gt;

&lt;p&gt;Lambda supplies different &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" rel="noopener noreferrer"&gt;runtimes&lt;/a&gt; depending on what language you want to write your function in. For Python programming, this runtime includes the Python standard library, but what if you want to use external packages from &lt;a href="https://pypi.org/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt; or elsewhere? This is something you can do with Lambda layers! Layers provide dependencies (or even a custom runtime) for your function, and in this quick tutorial we'll walk through how to use them!&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account and a little experience using Lambda functions. If you haven't used them before, Amazon has &lt;a href="https://aws.amazon.com/getting-started/hands-on/run-serverless-code/" rel="noopener noreferrer"&gt;great tutorials&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed on your computer, as we'll use that to install dependencies in a similar environment to the AWS Lambda Python runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our demo project will be a very simple Lambda function that accesses the &lt;a href="https://pokeapi.co/" rel="noopener noreferrer"&gt;PokéAPI&lt;/a&gt;'s &lt;code&gt;/api/v2/pokemon&lt;/code&gt; endpoint to return information about a Pokémon, given a name passed in the event that triggers the function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up our project locally
&lt;/h2&gt;

&lt;p&gt;First we'll create a &lt;a href="https://docs.python.org/3/tutorial/venv.html" rel="noopener noreferrer"&gt;virtual environment&lt;/a&gt; for our project and work in there, to keep any project dependencies separate from others on your computer:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;poke-lambda 
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;poke-lambda
&lt;span class="nv"&gt;$ &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate


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

&lt;/div&gt;

&lt;p&gt;We're using the &lt;a href="https://requests.readthedocs.io/en/master/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; library to do our API calls, so install that with &lt;code&gt;pip install requests&lt;/code&gt;. Our one file, &lt;code&gt;lambda_function.py&lt;/code&gt; looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Make sure a Pokemon name (or ID) was passed in the event object:
&lt;/span&gt;    &lt;span class="n"&gt;pokemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pokemon&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Missing pokemon attribute in the event object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# If we have a pokemon name/ID passed in, try to get info for it:
&lt;/span&gt;    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://pokeapi.co/api/v2/pokemon/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Could not load information for Pokemon &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Return what we got from the API:
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;What this code does is check for the &lt;code&gt;pokemon&lt;/code&gt; attribute in the &lt;code&gt;event&lt;/code&gt; data the Lambda function receives, queries the PokéAPI for information about the Pokémon passed in, and returns the API's response back (with basic error handling). At this point our project structure looks like this:&lt;/p&gt;

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

poke-lambda
|   lambda_function.py
└───venv


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Building our Lambda layer
&lt;/h2&gt;

&lt;p&gt;In order to put this code on AWS Lambda, we need a way to include the &lt;code&gt;requests&lt;/code&gt; library first! Lambda layers are .zip files containing libraries (and a custom runtime if you need) your function requires. For Python lambda layers, AWS &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html" rel="noopener noreferrer"&gt;requires&lt;/a&gt; libraries to be placed in either the &lt;code&gt;python&lt;/code&gt; or &lt;code&gt;python/lib/python-3.x/site-packages&lt;/code&gt; folders. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;While the virtual environment is activated for your project, save a list of its dependencies to a &lt;code&gt;requirements.txt&lt;/code&gt; file: &lt;code&gt;pip freeze &amp;gt; requirements.txt&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;python&lt;/code&gt; folder. This will house all the libraries to go in our layer. At this point, your folder structure should be:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

poke-lambda
| lambda_function.py
| requirements.txt
└───venv
└───python
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
3. From the project root, run the following command to build your dependencies in a container similar to the Lambda execution environment Amazon provides (courtesy of [lambci](https://hub.docker.com/u/lambci)):

    ```shell


    $ docker run --rm \
    --volume=$(pwd):/lambda-build \
    -w=/lambda-build \
    lambci/lambda:build-python3.8 \
    pip install -r requirements.txt --target python


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

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Let's break down what this command does, argument-by-argument:
- `--rm`: Makes sure that once the Docker container exits, it's removed (keeps things clean)
- `--volume`: [Bind mounts](https://docs.docker.com/storage/bind-mounts/) the current working directory to the `/lambda-build` directory on the container (this allows the container to access the requirements.txt file we've just generated, and add files to the `python` directory). If you're on Windows, you can also paste in a full path to your project root instead of `$(pwd)`.
- `-w`: Sets the working directory in the container (in this case, to our project root)
- `lambci/lambda:build-python3.8`: The docker image to run this container from -- make sure it matches the Python version you're working with! For reference, Amazon currently provides runtimes for Python 2.7, 3.6, 3.7 and 3.8 (I'm using 3.8 here)
- `pip install -r requirements.txt --target python`: Installs the project dependencies as normal, from the requirements.txt file to the `python` folder (more on that [here](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-t))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You should see some output from &lt;code&gt;pip&lt;/code&gt;. This is running in the Docker container. If successful, you should have dependencies installed to the &lt;code&gt;python&lt;/code&gt; folder you created earlier! Mine looks like this:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

poke-lambda
| lambda_function.py
| requirements.txt
└───venv
└───python
    └───bin
    └───certifi
    └───certifi-2020.11.8.dist-info
    └───chardet
    └───chardet-3.0.4.dist-info
    └───idna
    └───...
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
5. At this point, all we have to do is zip our `python` folder: `zip -r layer python/`. This will create a `layer.zip` file in your project's root directory. 

6. Next, upload the .zip file to Lambda! Sign into your AWS Console and head over to Services &amp;gt; Lambda &amp;gt; Layers (it should be under "Additional resources"). Click "Create layer" and give your layer a name! Pick the runtime that corresponds to the version of Python you're trying to use in your function, and upload the .zip file you created earlier:

![Filling out the name, runtime and layer .zip file in the Create Layer form](https://dev-to-uploads.s3.amazonaws.com/i/cgv94fjghy0tfy3vf7y0.png)

## Adding the layer to our Lambda function

We'll walk through this using the front-end console, but you can totally do this all over the AWS CLI!

1. While in your AWS Lambda console, click "Create function". Set it up however you want (pick a Python runtime though)

2. In the Lambda function designer, click on the "Layers" button:

    ![Clicking on the Layers button in the designer](https://dev-to-uploads.s3.amazonaws.com/i/gecow286pxsz8eqebxi0.png)

3. A table full of layers your function is using should appear below the designer. There aren't any so far, so lets add ours! Click "Add a layer" and choose "Custom layers". You should be able to see your layer you created earlier in the dropdown. Select and add it!

4. Once added, you'll be taken to the Designer again. Scroll down and paste your function code in (you can alternatively upload a .zip file containing just the `lambda_function.py` file since that's all we need here)

    ![Pasting in our function code from before](https://dev-to-uploads.s3.amazonaws.com/i/6inl9dz4julagqrpe8ei.png)

5. Save and click "Deploy" to update your function's code. Let's send our code a test event! At the top of the page, click on "Configure test events" under "Select a test event". We'll create an event payload to fetch details about Charizard:

    ![Configuring the test event for the Lambda function. This is a JSON payload with only one key (pokemon) equal to Charizard](https://dev-to-uploads.s3.amazonaws.com/i/4fl59jxnbpzol9uj8aki.png)

6. Save your test event, pick it from the dropdown and click "Test". You can now see your function returning results!

    ![Testing the Lambda function with the test event configured in step 5 should result in a success](https://dev-to-uploads.s3.amazonaws.com/i/w1pzxxndhl85kwxl5dtn.png)

That's all there is to it! Keep in mind functions can have multiple Lambda layers (up to 5 right now) and functions can share layers too.

In this tutorial we:

- Wrote a simple Lambda function that calls an external API and returns the results
- Created a Lambda layer containing all dependencies for our project that we can reuse in other projects later, using the [lambci/lambda](https://hub.docker.com/r/lambci/lambda) images and the Lambda GUI

Keep in mind all of this can also be accomplished over the AWS CLI (uploading the function, creating the layer and so on) if you prefer this method. There are lots of [AWS docs](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-using) which document this pretty well!

Hope you enjoyed this tutorial! Let me know if it was helpful in your project 😄
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tutorial: VPN on Demand with Siri, Shortcuts, Python, AWS EC2 &amp; Lambda</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Wed, 29 Jul 2020 14:42:33 +0000</pubDate>
      <link>https://forem.com/mmascioni/tutorial-vpn-on-demand-with-siri-shortcuts-python-aws-ec2-lambda-i83</link>
      <guid>https://forem.com/mmascioni/tutorial-vpn-on-demand-with-siri-shortcuts-python-aws-ec2-lambda-i83</guid>
      <description>&lt;p&gt;Sometimes you need a VPN for a short period of time (like when you'll be on public Wi-Fi for a little while). &lt;a href="https://aws.amazon.com/ec2/"&gt;Amazon EC2&lt;/a&gt; makes deploying servers to be used as VPNs for this purpose pretty simple and cost-effective since you're only charged from the time you start until the instance's termination. Open-source projects like &lt;a href="https://github.com/hwdsl2/setup-ipsec-vpn"&gt;hwdsl2/setup-ipsec-vpn&lt;/a&gt; make this even easier. However, this can require you to get onto the AWS Console to get everything set up, which can sometimes be a pain. The motivation for this project was to be able to do this on the go:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HgRh2M_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/blisasa6354jham8884w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HgRh2M_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/blisasa6354jham8884w.gif" alt="Asking Siri to create a VPN for me, then returning the VPN's IP address to connect to" width="374" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(It works on iOS 13 too 😊)&lt;/p&gt;

&lt;p&gt;To make this work, we're going to be deploying some Python code as a Lambda function on AWS. This code will start/stop/list EC2 instances for us based on an EC2 template we'll create. This template will run the &lt;a href="https://github.com/hwdsl2/setup-ipsec-vpn"&gt;setup-ipsec-vpn&lt;/a&gt; script on new instances automatically. We'll use &lt;a href="https://aws.amazon.com/api-gateway/"&gt;AWS API Gateway&lt;/a&gt; to create a RESTful API that will trigger the Lambda function. Finally, we'll use Apple's &lt;a href="https://apps.apple.com/us/app/shortcuts/id915249334"&gt;Shortcuts&lt;/a&gt; app to make an HTTP request to the API endpoint we create. Visually, this is what's going on:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y4RdmvHq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3mq0bj8lhlryxhcev9rz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y4RdmvHq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3mq0bj8lhlryxhcev9rz.png" alt="A small diagram depicting the flow of information between the different AWS services" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Icons used above can be found at &lt;a href="https://aws.amazon.com/architecture/icons/"&gt;AWS&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://aws.amazon.com"&gt;Amazon AWS account&lt;/a&gt;. If this is your first time signing up, you'll be eligible for the &lt;a href="https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc"&gt;free tier&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;A computer with &lt;a href="https://python.org"&gt;Python 3&lt;/a&gt; and the &lt;a href="https://aws.amazon.com/cli/"&gt;AWS CLI&lt;/a&gt; installed and &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html"&gt;configured&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An iOS device with the &lt;a href="https://apps.apple.com/us/app/shortcuts/id915249334"&gt;Shortcuts&lt;/a&gt; app installed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations/Disclaimer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You'll be limited to deploying only in one AWS region at a time, since EC2 Launch Templates are tied to a region. In this tutorial, we use &lt;code&gt;us-east-1&lt;/code&gt; as our region.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;About AWS charges:&lt;/strong&gt; Some of the services used in this post are intended to fall within the free tier usage, however some do not. Lambda offers 1 million Lambda requests every month even once your free-tier access expires, while API Gateway only offers 1 million requests per month for the first year, and EC2 offers 750 hours per month on t2.micro instances (what we're using) for the first year. Make sure to review pricing for &lt;a href="https://aws.amazon.com/lambda/pricing/"&gt;Lambda&lt;/a&gt;, &lt;a href="https://aws.amazon.com/api-gateway/pricing/"&gt;API Gateway&lt;/a&gt; and &lt;a href="https://aws.amazon.com/ec2/pricing/on-demand/"&gt;EC2&lt;/a&gt; before continuing. Also, as we test things out in this tutorial, keep an eye on your AWS console to make sure anything you created to test with gets deleted to avoid incurring extra charges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This is more of a demo so far than anything else! I'd love any feedback on how to make it more secure or user-friendly in the future.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the EC2 Launch Template
&lt;/h2&gt;

&lt;p&gt;EC2 Launch Templates are a great way to save a frequently used EC2 launch configuration (i.e. instance type, security groups, firewall rules, storage, etc.). In our case our launch template will have all the necessary configuration necessary to deploy an EC2 instance as a VPN for us.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Sign into your &lt;a href="https://console.aws.amazon.com"&gt;AWS Console&lt;/a&gt; and ensure you're in the region you want to deploy your VPNs to (you can see this at the top right of the screen). In this guide we use &lt;code&gt;us-east-1&lt;/code&gt; (North Virginia). Head over to Services &amp;gt; EC2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;First, we'll set up a &lt;strong&gt;Security Group&lt;/strong&gt;, which contains the firewall rules for our EC2 instance. On the left side of the page, click on "Security Groups" under "Network and Security. Click "Create Security Group".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give your group a name, leave the VPC dropdown at its default, and set the following &lt;em&gt;inbound&lt;/em&gt; UDP rules (leave Outbound at its default): this will allow you to connect to your server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3WCNkKGF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/5i6wqwck97o5wty9kbg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3WCNkKGF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/5i6wqwck97o5wty9kbg9.png" alt="Opening up UDP ports 500 and 4500" width="750" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Note: For greater security, you can restrict &lt;code&gt;Source&lt;/code&gt; further here: perhaps to a range of IP addresses)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save the security group. Next we'll create a key pair, useful if you want to SSH into your instance in the future. Under "Network &amp;amp; Security", click on "Key Pairs".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "Create Key Pair" and follow the instructions (if you already have set up one, you can use that key pair instead for the remainder of this guide)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now we'll create our launch template. Under "Instances", click on "Launch Templates", and then "Create launch template". Give the template a name, and begin filling out the form. For the AMI, I chose one that corresponded to Ubuntu 18.04 (you can check what OS's are currently being supported by &lt;a href="https://github.com/hwdsl2/setup-ipsec-vpn#requirements"&gt;hwdsl2/setup-ipsec-vpn&lt;/a&gt;). I filled out the following details:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instance Type: t2.micro (Free Tier eligible)
Key pair: The key pair you created in step (5)
Security groups: The security group you created in step (3)
Storage (volumes): If a volume wasn't added automatically once you picked the AMI, click "Add new volume". Make sure "Delete on termination" is set to Yes, and leave everything else at default.
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under "Resource tags", click "Add tag" and set a key of &lt;code&gt;instance_type&lt;/code&gt; to &lt;code&gt;vpn&lt;/code&gt;. Leave "Resource types" at its default (should have "Instances" selected). This will cause EC2 instances deployed with this template to be tagged, which lets us keep them separate from other instances and makes sure our Lambda function terminates the right instance when asked to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under "Advanced details", scroll to "User data". This is where we'll put the script from &lt;a href="https://github.com/hwdsl2/setup-ipsec-vpn#installation"&gt;hwdsl2/setup-ipsec-vpn&lt;/a&gt; in. Make sure to put &lt;code&gt;#!/bin/bash&lt;/code&gt; at the top of the script since this is being executed in a Bash shell. It's executed when your instance is booted for the first time. To find out more about User Data, check the &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-shell-scripts"&gt;Amazon docs&lt;/a&gt; on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5flwDggj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/pwexe7q9vb46otopntpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5flwDggj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/pwexe7q9vb46otopntpt.png" alt="User data to input to your template" width="523" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill out &lt;code&gt;VPN_IPSEC_PSK&lt;/code&gt;, &lt;code&gt;VPN_USER&lt;/code&gt; and &lt;code&gt;VPN_PASSWORD&lt;/code&gt; with an IPSec PSK (should be &amp;gt;20 random characters), a VPN username and password. Generate these randomly and store them in a password manager. Every time an instance is created, these credentials will be used and only the IP address will change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: More secure than using user data here may be to have this shell script in a secured S3 bucket accessed at launch time or pass the user data at launch time with Lambda, pulling credentials from Lambda environment variables.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save the launch template and note the name you gave to it. Also note the launch template ID. If you'd like to test and see if it works, you can &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-templates.html"&gt;launch an EC2 instance based on it&lt;/a&gt;! Follow the connection instructions &lt;a href="https://github.com/hwdsl2/setup-ipsec-vpn/blob/master/docs/clients.md"&gt;here&lt;/a&gt; after it's been up and running for a few minutes (it can take a little bit as the script is installing/updating packages)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Otherwise, let's move on to the Lambda part of the project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up our Lambda function
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/lambda/"&gt;Amazon Lambda&lt;/a&gt; is a service that allows you to write code and execute it using various triggers (here we use an HTTP request). What's so neat about it is you're only charged for the time and resources your code uses while it runs, saving quite a bit. Here, we're using some Python code to automatically launch an EC2 instance based on the template we defined above (or terminate/list instances).&lt;/p&gt;

&lt;p&gt;I've already written the code and you can check it out on &lt;a href="https://github.com/mm/siri-shortcuts-vpn"&gt;GitHub here&lt;/a&gt;. It uses the &lt;a href="https://flask.palletsprojects.com/en/1.1.x/"&gt;Flask&lt;/a&gt; framework to define a tiny API that can receive HTTP requests and act on them accordingly. We'll package this up and deploy it as a Lambda function with the popular &lt;a href="https://github.com/Miserlou/Zappa"&gt;Zappa&lt;/a&gt; package a little later. Before that, we need to ensure our function will have the right permissions and make sure it works locally!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the necessary IAM policy
&lt;/h3&gt;

&lt;p&gt;For Lambda to be able to perform EC2 actions on your behalf (starting servers up, shutting them down...) it needs permissions to do so. When using AWS, we define these permissions by an &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html"&gt;IAM policy&lt;/a&gt;. We'll set one up here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Log in to your AWS Console &amp;gt; Services &amp;gt; IAM. Select "Policies" from the sidebar, and then "Create policy". Switch from the visual editor to JSON mode and replace the policy in the text editor with the following:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VisualEditor0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2:Describe*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:RunInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:CreateTags"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"ArnLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"ec2:LaunchTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:AWS_REGION:ACCOUNT_ID:launch-template/LAUNCH_TEMPLATE_ID"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VisualEditor2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2:CreateTags"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:*:ACCOUNT_ID:instance/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"ec2:CreateAction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RunInstances"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VisualEditor3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:TerminateInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:StartInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:StopInstances"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"ec2:ResourceTag/instance_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vpn"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Fill in the &lt;code&gt;AWS_REGION&lt;/code&gt;, &lt;code&gt;ACCOUNT_ID&lt;/code&gt; and &lt;code&gt;LAUNCH_TEMPLATE_ID&lt;/code&gt; with the AWS region you made your EC2 launch template in (for example, I used &lt;code&gt;us-east-1&lt;/code&gt;), your AWS account ID (numeric) and your launch template ID from the last section.&lt;/p&gt;

&lt;p&gt;This policy allows your function to describe details about any EC2 instance, create new instances when your launch template was specified, create tags on an instance during its creation, and only start/stop/terminate instances tagged as VPNs. For more examples of policies if you're interested (as they relate to EC2 instances), &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ExamplePolicies_EC2.html#iam-example-runinstances"&gt;Amazon's docs&lt;/a&gt; have a wealth of info. With this policy, any parameters in your launch template &lt;em&gt;can&lt;/em&gt; be overridden at launch time (ideally you'd want to lock this down further).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "Review policy" and give your policy a name. Then click "Create policy" to save the policy.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a test IAM user with the IAM policy
&lt;/h3&gt;

&lt;p&gt;This step allows us to test out the code locally before deploying. Here we'll create an IAM &lt;em&gt;user&lt;/em&gt; and attach our &lt;em&gt;policy&lt;/em&gt; to it. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Log in to your AWS Console &amp;gt; Services &amp;gt; IAM. Select "Users" from the sidebar, and click "Add user".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Give the user a username (doesn't matter) and make sure &lt;strong&gt;Programmatic access&lt;/strong&gt; is checked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Next, and click "Attach existing policies directly". Search for your policy, ensure it's checked and click Next again. You can leave tags blank. Finally, click Create user. Make note of the &lt;em&gt;Access key ID&lt;/em&gt; and &lt;em&gt;Secret access key&lt;/em&gt; -- we will be using these shortly.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Working with the code locally
&lt;/h3&gt;

&lt;p&gt;With all of the policy setup out of the way, we're ready to go! Now, we'll deploy a web service I wrote using Flask to Lambda. First, we'll test the code locally, and once it's working, we'll use &lt;a href="https://github.com/Miserlou/Zappa"&gt;Zappa&lt;/a&gt; to automate deployment. These instructions are also on my &lt;a href="https://github.com/mm/siri-shortcuts-vpn"&gt;GitHub repo&lt;/a&gt; for this project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ensure you've installed the &lt;a href="https://aws.amazon.com/cli/"&gt;AWS CLI&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html"&gt;configured your environment&lt;/a&gt; and have Python 3 and pip ready to go for local development. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clone this repository in a directory of your choosing: &lt;code&gt;git clone git@github.com:mm/siri-shortcuts-vpn.git&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I recommend setting up a &lt;a href="https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/"&gt;virtual environment&lt;/a&gt; for this project to keep its dependencies/versions of those dependencies separate from other projects. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once in the project folder, with your virtual environment activated, install the packages this code relies on: &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create an &lt;code&gt;.env&lt;/code&gt; file in the root folder of the project and fill in the following environment variables. This will allow you to simulate using the IAM policy you created in tutorial to ensure it works before deploying.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_ACCESS_KEY_ID=access_key_id_you_just_created
AWS_SECRET_ACCESS_KEY=secret_access_key_you_just_created
AWS_DEFAULT_REGION=fill_in_your_aws_region_here_(e.g us-east-1)
LAUNCH_TEMPLATE_NAME=fill_in_your_launch_template_name_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If everything is in order, you should be able to run &lt;code&gt;python3 app.py&lt;/code&gt; in the root directory to start up the Flask development server. Look out for the command's output. By default, the server will be listening for connections on port 5000.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Try making HTTP requests to your server! You can use cURL, &lt;a href="https://httpie.org"&gt;httpie&lt;/a&gt;, &lt;a href="https://paw.cloud"&gt;Paw&lt;/a&gt;, &lt;a href="https://www.postman.com"&gt;Postman&lt;/a&gt; or any API testing tool you'd like. For example, if I was trying to deploy instances in the US-East-1 region (and that's where my launch template was stored), with cURL I'd make this request to try starting up:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:5000/instances/us-east-1
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If successful I should get a response like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"instance_id":"i-some-instance-id","ip":"&amp;lt;some ip address&amp;gt;","region":"us-east-1"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Getting running instances can be done via a GET request (i.e.&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;curl -X GET http://localhost:5000/instances/us-east-1&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
) and terminating instances in a region can be done via a DELETE request (&lt;br&gt;
&lt;br&gt;
&lt;code&gt;curl -X DELETE http://localhost:5000/instances/us-east-1&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
). The code attempts to make sure only 1 instance is running at a time (to protect against accidentally starting many instances at once) -- so DELETE works as intended (deletes the whole collection of instances, but that collection is only supposed to contain 1 instance at a time).&lt;/p&gt;

&lt;p&gt;Once you're sure everything is working okay, let's deploy to Lambda!&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying to Lambda and API Gateway
&lt;/h2&gt;

&lt;p&gt;For deployment, we could totally wrap up our Flask app ourselves and set up API Gateway to send requests to it. However, this would take a little time and is a bit beyond the scope of this tutorial. &lt;a href="https://github.com/Miserlou/Zappa"&gt;Zappa&lt;/a&gt; is an open-source Python tool I've been using for a while now. It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrap your code up (including all dependencies) in a &lt;code&gt;.zip&lt;/code&gt; package that is automatically uploaded to Lambda&lt;/li&gt;
&lt;li&gt;Create an &lt;a href="https://aws.amazon.com/api-gateway/"&gt;AWS API gateway&lt;/a&gt; endpoint (and help you secure it with an API key/another form of authorization). This endpoint is set up as a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html"&gt;Lambda proxy integration&lt;/a&gt;, where API Gateway passes all request data (method, headers, etc.) to your Lambda function transparently. This means you can set up routing in your Flask, Django or (insert framework here) app and not have to fiddle too much with routing in API Gateway. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... among other things! I highly recommend checking out the docs for Zappa, it's an extremely useful tool for serverless apps like this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;While in the project directory from before (and in your virtual environment), run &lt;code&gt;zappa init&lt;/code&gt; in your terminal. You should get something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_aezvo6p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/7qxz66yplgsq126f04d1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_aezvo6p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/7qxz66yplgsq126f04d1.png" alt="After running zappa init, you should be presented with a setup dialog in your terminal" width="744" height="298"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go through the setup steps and keep the default options as you go through. Open up your &lt;code&gt;zappa_settings.json&lt;/code&gt; file. Mine looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"app_function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aws_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"profile_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"project_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"siri-shortcuts-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"s3_bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zappa-9bh21r44r"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




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

&lt;p&gt;First, ensure your &lt;code&gt;aws_region&lt;/code&gt; value matches the AWS region your EC2 template is located in. If not, change that (I've been using &lt;code&gt;us-east-1&lt;/code&gt; throughout this tutorial). Next, we're going to add two keys: &lt;code&gt;api_key_required&lt;/code&gt; (to secure our API with an API key) and another key &lt;code&gt;aws_environment_variables&lt;/code&gt; to set environment variables in the Lambda environment (currently we only need one for our launch template name):&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"app_function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aws_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"profile_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"project_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"siri-shortcuts-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"s3_bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zappa-9bh21r44r"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"api_key_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aws_environment_variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"LAUNCH_TEMPLATE_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your launch template name here (not ID)"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




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

&lt;p&gt;Save &lt;code&gt;zappa_settings.json&lt;/code&gt;. We should be good to go! Assuming you named your stage &lt;code&gt;dev&lt;/code&gt; (as in Zappa setup), you can run &lt;code&gt;zappa deploy dev&lt;/code&gt; in your terminal! (Side note: Zappa supports deploying multiple stages! i.e. You could have totally different environment variables/security settings for the dev vs. prod vs. staging vs. whatever stage you want. Very cool!) This could take a few minutes while dependencies are packaged and uploaded. There should be a bunch of output. At the end, you should see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Deploying API Gateway..
Created a new x-api-key: zgjcmuieo1
Deployment complete!: https://bsy9qfjo7k.execute-api.us-east-1.amazonaws.com/dev
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That URL in the Zappa output is actually part of our endpoint URL! We'll be making requests to it soon. As you can see from the last step, an API Key was created for our project, but it hasn't been associated with our endpoint yet. Let's secure that now. Sign into your AWS Console &amp;gt; API Gateway. You should see your project listed as an API! If not, ensure your region is set to the one you used in &lt;code&gt;zappa_settings.json&lt;/code&gt;. Click on it, and then click on Usage Plans.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new usage plan (it can be named whatever you want), and enable throttling. I keep mine restrictive since I'm the only one supposed to be using it anyway: at 1 request per second and 1000 per month. Hit "Next."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add your API there! Click "Add API Stage" and then choose your API from the list, as well as the &lt;code&gt;dev&lt;/code&gt; stage. Click the grey checkmark and hit "Next". Click "Add API Key to Usage Plan". &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;For the "Name", search &lt;code&gt;dev_&lt;/code&gt; plus the beginning of your endpoint URL. For example, Zappa outputted &lt;code&gt;https://bsy9qfjo7k.execute-api.us-east-1.amazonaws.com/dev&lt;/code&gt; in step 4, so I chose &lt;code&gt;dev_bsy9qfjo7k&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4YR_HfUF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/hei0iggjensny7zsepe0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4YR_HfUF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/hei0iggjensny7zsepe0.png" alt="Searching for my API key using the endpoint Zappa gave me" width="584" height="252"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hit the grey checkmark, click "Done" and you're good to go! You should see a list of Usage Plans on your account. Click the one you just created then "API Keys". Click the API key you added, and then click "Show" where it says &lt;strong&gt;API key&lt;/strong&gt;. Make note of this key as it's the key you'll use to make requests!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Head to Services &amp;gt; Lambda in your console. Again, you should see the Zappa-created Lambda function there. Click on it, then click on "Permissions". You should see an execution role listed there. Click on it. This is the role your Lambda function uses, and to add extra permissions to it (the ability to launch and shut down EC2 instances), we'll attach the policy we created earlier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the "Attach policies" button, and search for the IAM policy you created a few sections back. Select it, and click "Attach policy". That's all!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After all this work, it's finally time to test it! Here I'm using cURL, but you can use whatever API testing tool you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: api_key_here"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; GET https://bsy9qfjo7k.execute-api.us-east-1.amazonaws.com/dev/instances/us-east-1
&lt;span class="go"&gt;
{"region":"us-east-1","running_instances":[]}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we want to start up an instance, we POST to the endpoint instead. We can see we get back the server's IP in the response (we can use this to connect!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: api_key_here"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://bsy9qfjo7k.execute-api.us-east-1.amazonaws.com/dev/instances/us-east-1
&lt;span class="go"&gt;
{"instance_id":"i-09912cc4f30e020d7","ip":"54.236.255.219","region":"us-east-1"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if you try and run this when a VPN is already running, the function will return a 429 - Too Many Requests response instead. This is to prevent accidentally starting too many instances up and incurring additional charges.&lt;/p&gt;

&lt;p&gt;To connect to your instance, wait a few minutes and then follow &lt;a href="https://github.com/hwdsl2/setup-ipsec-vpn/blob/master/docs/clients.md"&gt;this guide&lt;/a&gt;. Use the IP address your function outputs, as well as the key, username and password you set way back when you made the EC2 launch template! You should have an up and running VPN.&lt;/p&gt;

&lt;p&gt;If we want to delete the instance, we can send a DELETE request and get some info about how many instances we terminated and where (or, we could go to our EC2 dashboard manually as always)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: api_key_here"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE https://bsy9qfjo7k.execute-api.us-east-1.amazonaws.com/dev/instances/us-east-1
&lt;span class="go"&gt;
{"instances_terminated":1,"region":"us-east-1"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If ever you want to remove everything (delete the endpoint, Lambda function and so on), you can run &lt;code&gt;zappa undeploy dev&lt;/code&gt; from the project directory (or just remove from the AWS Console directly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Making use of our API with Shortcuts and Siri
&lt;/h2&gt;

&lt;p&gt;Since we've created a web service, technically anything capable of making an HTTP request (and passing headers for our API key) should be able to trigger our function. For this guide, we'll use &lt;a href="https://apps.apple.com/us/app/shortcuts/id915249334"&gt;Shortcuts&lt;/a&gt;, an iOS app by Apple. Of the many actions Shortcuts has, "Get Contents of URL" is the primary workhorse we'll be using as it can make arbitrary HTTP requests and output the results. I've made 3 shortcuts for you to use and look through how they work. Click on them to install the shortcut on your iOS device (it'll ask a few questions on import like what your endpoint URL and API key are) and review what they do before running them. You might have to change your device's settings to allow untrusted shortcuts to run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.icloud.com/shortcuts/1b8b413d86a344aabde5930c5efc05f3"&gt;Start EC2 VPN&lt;/a&gt;: This will make a POST request to your endpoint to start a new server up. If successful, it will copy the server's IP to your clipboard.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.icloud.com/shortcuts/a59e74bd60fc4af49a2d71738756bbd5"&gt;List running VPNs&lt;/a&gt;: This will make a GET request to your endpoint, and afterwards return the number of servers running (should at most be 1) and copy the IP address to your clipboard.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.icloud.com/shortcuts/eb95f06e7f5e4ecaa292e1639530b233"&gt;Terminate EC2 VPN&lt;/a&gt;: This will terminate all VPN instances (with a DELETE request) running in a given region, and tell you how many instances were terminated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get Siri up and running, simply tell Siri to "Run Shortcut Name", for example "Run Start EC2 VPN" to launch a new VPN. I'd give the server some time before trying to connect to it (in my tests one took close to 5 minutes once). You can follow the instructions at &lt;a href="https://github.com/hwdsl2/setup-ipsec-vpn/blob/master/docs/clients.md"&gt;hwdsl2/setup-ipsec-vpn&lt;/a&gt; to get connected!&lt;/p&gt;

&lt;p&gt;That's all there is to it! Thanks for checking out this tutorial/demonstration; it's been helpful for me in the past and was fun to try out so I hope it helps you too! This is one of the first technical tutorials I've written publicly, so I'd really appreciate any feedback you have!&lt;/p&gt;

&lt;p&gt;Huge thanks to &lt;a href="https://github.com/hwdsl2"&gt;Lin Song&lt;/a&gt; for writing the setup-ipsec-vpn setup script our template uses and documentation for connecting! 😊&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>aws</category>
    </item>
    <item>
      <title>Heartbridge: A command-line tool to transfer iOS Health heart rate data to your computer</title>
      <dc:creator>Matt Mascioni</dc:creator>
      <pubDate>Thu, 21 May 2020 21:22:57 +0000</pubDate>
      <link>https://forem.com/mmascioni/heartbridge-a-command-line-tool-to-transfer-ios-health-heart-rate-data-to-your-computer-21h8</link>
      <guid>https://forem.com/mmascioni/heartbridge-a-command-line-tool-to-transfer-ios-health-heart-rate-data-to-your-computer-21h8</guid>
      <description>&lt;h2&gt;
  
  
  My Final Project: Heartbridge
&lt;/h2&gt;

&lt;p&gt;This was a small project I put together this year (final year of engineering woo!) I wanted an easy way to transfer heart rate readings from my iPhone/Apple Watch to my Mac so I could play around with the data using something like pandas or visualize it differently. This command-line tool is the result! It can export data as a CSV or JSON file depending on what arguments get passed in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--otq1p9qg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.imgur.com/29QeZT4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--otq1p9qg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.imgur.com/29QeZT4.gif" alt="Using the tool to receive heart rate data from my iPhone" width="664" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Link to Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mm"&gt;
        mm
      &lt;/a&gt; / &lt;a href="https://github.com/mm/heartbridge"&gt;
        heartbridge
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Using the iOS Shortcuts app and Python together to export iOS Health data to a JSON/CSV file.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Heartbridge: iOS Health Data Export&lt;/h1&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mm/heartbridge/actions/workflows/python-package.yml/badge.svg"&gt;&lt;img src="https://github.com/mm/heartbridge/actions/workflows/python-package.yml/badge.svg" alt="Python Package Tests"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Heartbridge is a command-line tool that exports health data from your iOS device to your local computer, with the help of an iOS Shortcut. It supports exporting many types of data from the Health app, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heart Rate&lt;/li&gt;
&lt;li&gt;Resting Heart Rate&lt;/li&gt;
&lt;li&gt;Heart Rate Variability&lt;/li&gt;
&lt;li&gt;Steps&lt;/li&gt;
&lt;li&gt;Flights Climbed&lt;/li&gt;
&lt;li&gt;Cycling Distance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Heartbridge receives data from the &lt;a href="https://apps.apple.com/us/app/shortcuts/id915249334" rel="nofollow"&gt;Shortcuts&lt;/a&gt; app (via HTTP), automatically exports it to the directory of your choosing (in CSV or JSON format) and automatically names files according to the health data type and date range they cover. Exported files contain a time stamp ("Start Date" in Health) and reading ("Value" in Health). To read more about how the file exports look depending on the data type, check out &lt;a href="https://github.com/mm/heartbridge#data-type-support"&gt;Data Type Support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you don't want to use the built-in CLI or server, you can also use Heartbridge to parse data from Shortcuts directly-- for…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mm/heartbridge"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I built it/how it works
&lt;/h2&gt;

&lt;p&gt;This was built in Python (as of now it only depends on what comes with the Python standard library), and makes use of an iOS shortcut (using the native Shortcuts app) I wrote as well. The Python package, when run will start accepting HTTP requests at a specified port (defaults to 8888). &lt;/p&gt;

&lt;p&gt;When the shortcut is run on the user's iPhone, they'll be asked to select a date range of heart rate data they want to export. Readings will be sent in the body of an HTTP POST request to that same port on their computer, provided they're on the same network. The JSON in the request body is processed further by Heartbridge once it is received, and the readings are exported to either a CSV or JSON file for further exploration. &lt;/p&gt;

&lt;p&gt;This project was a great way to learn about using a continuous integration tool for unit testing (to make sure my unit tests pass with every commit I make). For this I used &lt;a href="https://travis-ci.com"&gt;Travis CI&lt;/a&gt; to hook into my GitHub repo and run code in my commits against the unit tests I had written. This was offered as part of the GitHub Student Developer Pack and I loved using it!&lt;/p&gt;

&lt;p&gt;This was also my first time submitting my code as a package on &lt;a href="https://pypi.org"&gt;PyPI&lt;/a&gt;, which allows anyone with &lt;code&gt;pip&lt;/code&gt; to install it at the command line and run it easily. This required me to structure my code as an overall package, which I've always wanted to learn and finally got a chance to with this project. I'm excited to create more of them in the future!&lt;/p&gt;

</description>
      <category>octograd2020</category>
      <category>githubsdp</category>
    </item>
  </channel>
</rss>
