<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Emily Chen</title>
    <description>The latest articles on DEV Community by Emily Chen (@emsesc).</description>
    <link>https://dev.to/emsesc</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%2F518905%2F6efa4e79-4917-489b-884f-892f8ade105a.jpg</url>
      <title>DEV Community: Emily Chen</title>
      <link>https://dev.to/emsesc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/emsesc"/>
    <language>en</language>
    <item>
      <title>Using GitHub Actions to Test Student Code Submissions</title>
      <dc:creator>Emily Chen</dc:creator>
      <pubDate>Mon, 11 Jul 2022 02:37:06 +0000</pubDate>
      <link>https://dev.to/bitproject/using-github-actions-to-test-student-code-submissions-3ef</link>
      <guid>https://dev.to/bitproject/using-github-actions-to-test-student-code-submissions-3ef</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This blog is based on Bit Project's &lt;a href="https://github.com/apps/counselorbot" rel="noopener noreferrer"&gt;CounselorBot&lt;/a&gt;, which you can test out yourself!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We currently have two available courses:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bitprj/Intro-To-Serverless" rel="noopener noreferrer"&gt;Intro to Serverless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitprj/js-serverless-prep-course" rel="noopener noreferrer"&gt;Intro to JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get started with this &lt;a href="https://www.loom.com/share/7edcdb4f60a443ebbad80d2e7b962deb" rel="noopener noreferrer"&gt;video tutorial&lt;/a&gt; or this &lt;a href="https://github.com/bitprj/Intro-To-Serverless/blob/main/GETTING_STARTED.md" rel="noopener noreferrer"&gt;walkthrough page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; are often used for automated code operations, usually automatic deployments. However, they also have another handy-dandy use case: &lt;strong&gt;checking students' code submissions&lt;/strong&gt; so you (being lazy) don't have to run them yourself!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a future tutorial, we'll also talk about how output from GitHub Actions can be automatically commented on students' pull requests to provide feedback on what went wrong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Outlining Our Plan
&lt;/h2&gt;

&lt;p&gt;All of the workflow files used as examples in this tutorial can be found &lt;a href="https://github.com/bitprj/Intro-To-Serverless" rel="noopener noreferrer"&gt;here&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%2Fuser-images.githubusercontent.com%2F69332964%2F176958285-eb4830b6-cfbd-42fc-a053-e78873dd6b73.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%2Fuser-images.githubusercontent.com%2F69332964%2F176958285-eb4830b6-cfbd-42fc-a053-e78873dd6b73.png" alt="CounselorBot"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each "step" in our curriculum requires a different test. When a student (the user) commits to the repository, we need to...&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Figure out which workflow file to run&lt;/li&gt;
&lt;li&gt;Figure out which step the student is on&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;...in order to run the correct test.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alongside actually running the tests, we will also be using GitHub Actions to determine the answers to these two questions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Writing the YAML File
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Getting Started
on:
  push:
    branches:
      - hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first section of the &lt;a href="https://github.com/bitprj/Intro-To-Serverless/blob/main/.bit/workflows/hello.yml" rel="noopener noreferrer"&gt;YAML file&lt;/a&gt; gives the GitHub Action a display name and configures &lt;em&gt;when&lt;/em&gt; the action should run. In this case, it'll run when a commit is pushed to the &lt;code&gt;hello&lt;/code&gt; branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v2

      - name: Setup Node Environment
        uses: actions/setup-node@v2
        with:
          node-version: '14'

      - name: Install Dependencies
        run: |
          npm install minimist
          npm install node-fetch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next section provides other steps to "set up" our environment. We will mainly be using node to test code; &lt;strong&gt;npm dependencies&lt;/strong&gt; can also be installed if they will be used in the tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - name: Get Count
        id: vars
        run: echo ::set-output name=count::$(node ./.bit/scripts/getProgress.js --repo=${{github.event.repository.name}} --owner=${{github.repository_owner}} )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our first "unique" step in this workflow involves using &lt;a href="https://github.com/bitprj/Intro-To-Serverless/blob/main/.bit/scripts/getProgress.js" rel="noopener noreferrer"&gt;this script&lt;/a&gt; to retrieve the step that the student is currently on. This "count" value is an integer that correlates with steps in our curriculum.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the &lt;code&gt;echo ::set-output name=count::&lt;/code&gt; part sets the output of the file that runs to a local variable named &lt;code&gt;count&lt;/code&gt;, which will be accessed later with &lt;code&gt;steps.vars.outputs.count&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing Different Types Of Submissions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Now that the environment for our workflow is set up, we can run different &lt;em&gt;test files&lt;/em&gt;, which provide feedback on student code.&lt;/strong&gt; With GitHub Actions, you are essentially just running commands on a terminal. This opens up options from running JavaScript files to testing webpages to calling Azure Functions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All test examples used in this tutorial can be found in &lt;a href="https://github.com/bitprj/Intro-To-Serverless/tree/main/.bit/tests" rel="noopener noreferrer"&gt;this directory&lt;/a&gt; and &lt;a href="https://github.com/bitprj/Intro-To-Serverless/tree/main/.bit/tests/cypress/integration" rel="noopener noreferrer"&gt;this directory&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Testing Program Files (JS, Python)
&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%2Fuser-images.githubusercontent.com%2F69332964%2F177048987-a1f20cc9-1fda-4351-91ea-f4df9cf3aa97.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%2Fuser-images.githubusercontent.com%2F69332964%2F177048987-a1f20cc9-1fda-4351-91ea-f4df9cf3aa97.png" alt="test"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - name: Step 1
        if: ${{steps.vars.outputs.count == 1 &amp;amp;&amp;amp; github.event.head_commit.message != 'Update progress'}}
        run: |
          node .bit/tests/test.1.2.js --repo=${{github.event.repository.name}} --user=${{github.repository_owner}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step can be added onto the &lt;code&gt;steps&lt;/code&gt; section of the YAML file for workflow configuration. A &lt;strong&gt;conditional&lt;/strong&gt; statement is utilized to &lt;em&gt;only&lt;/em&gt; run this test if the "count" value is correct. The command &lt;code&gt;node .bit/tests/test.1.2.js&lt;/code&gt; would then execute &lt;a href="https://github.com/bitprj/Intro-To-Serverless/blob/main/.bit/tests/test.1.2.js" rel="noopener noreferrer"&gt;this test&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try { hello = require('./../../week1/helloworld.js') }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test first attempts to import the student's code in the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let helloworld = hello()
let test_output = "Hello World"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If successful, it will attempt to execute the imported functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await functions.throwError(`Got: '${helloworld}', was expecting: '${test_output}'.`, user, repo)
console.log(`Got: "${helloworld}", was expecting: "${test_output}".`)
process.exit(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Source code for the &lt;code&gt;functions.throwError()&lt;/code&gt; method can be found &lt;a href="https://github.com/bitprj/Intro-To-Serverless/blob/main/.bit/tests/functions.js" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It includes functions used to provide feedback to students and reduce repetition in tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Depending on its success, the test will provide feedback by throwing errors in the workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Webpages with Cypress
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; is a powerful testing tool that allows a simulation of a user's actions on a website.&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%2Fdocs.cypress.io%2F_nuxt%2Fimg%2Fcommand-log-of-dynamic-url-test.9da5db5.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%2Fdocs.cypress.io%2F_nuxt%2Fimg%2Fcommand-log-of-dynamic-url-test.9da5db5.png" alt="cypress"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe('Testing Bunnimage', () =&amp;gt; {
    it('Testing Week 4 Step 1', () =&amp;gt; {
        cy.visit('bunnimage/index.html')
        cy.get('input[type="text"]').type('console.log("hi yall")')
        cy.get('input[type="button"]').click()
        cy.get('#output').contains('console.log("hi yall")❤️')
    })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple &lt;a href="https://github.com/bitprj/Intro-To-Serverless/blob/main/.bit/tests/cypress/integration/4.1.spec.js" rel="noopener noreferrer"&gt;example of a test&lt;/a&gt; as shown here simulates typing &lt;code&gt;console.log("hi yall")&lt;/code&gt;, clicking the specified button on the page, and checking to see if the output equals &lt;code&gt;console.log("hi yall")❤️&lt;/code&gt;. In the workflow's output, feedback from Cypress is provided.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing APIs
&lt;/h3&gt;

&lt;p&gt;Student coded endpoints can also be tested with HTTP requests run in the workflow. The key to this method is asking students to add "repository secrets" &lt;strong&gt;that can then be accessed during the workflow&lt;/strong&gt; as environment variables using the below syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - name: Step 12
        if: ${{steps.vars.outputs.count == 12 &amp;amp;&amp;amp; github.event.head_commit.message != 'Update progress'}}
        env:
          MORSE_ENDPOINT: ${{ secrets.MORSE_ENDPOINT }}
        run: |
          npm install node-fetch
          node .bit/tests/test.1.8.js --repo=${{github.event.repository.name}} --user=${{github.repository_owner}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example accesses the student's &lt;code&gt;MORSE_ENDPOINT&lt;/code&gt; secret (this can be later accessed in the code with &lt;code&gt;uri = process.env.MORSE_ENDPOINT&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;try {
    const resp = await fetch(uri + "&amp;amp;plaintext=ilovebitproject", {
        method: 'GET'
    });
    var data = await resp.text()
    let test = JSON.stringify(data)
    functions.validateResponseStatus(resp, uri)
} catch (e) {
    console.error("We're having trouble making a request to your endpoint. Try again?")
    await functions.throwError("We're having trouble making a request to your endpoint. Try again?", user, repo)
    process.exit(1)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, utilizing the student's endpoint, an HTTP GET request is made to see if the endpoint is alive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (data.length &amp;lt; 3) {
    console.error("No response... Try again!")
    await functions.throwError("No response... Try again!", user, repo)
    process.exit(1)
} else if (data == answer) {
    console.info("Yay!🎉 Success - thanks for helping us on this top secret mission. Welcome to the team.")
    console.info(`We got "${answer}" with the input of ilovebitproject`)
} else {
    console.error(`YIKES! We got "${data}" instead of "${answer}". Try again!`)
    await functions.throwError(`YIKES! We got '${data}' instead of '${answer}'. Try again!`, user, repo)
    process.exit(1)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a response is received, the test attempts to determine if the output of the endpoint matches the correct "answer."&lt;/p&gt;

&lt;p&gt;As demonstrated in &lt;a href="https://github.com/bitprj/Intro-To-Serverless/blob/main/.bit/tests/test.1.8.js" rel="noopener noreferrer"&gt;this test&lt;/a&gt; shown above, multiple test cases can be at once with feedback returned to the student as output of the workflow.&lt;/p&gt;

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

&lt;p&gt;GitHub Actions are &lt;strong&gt;extremely versatile&lt;/strong&gt; as they allow developers to create their own environment, automating tasks and running scripts. Running on repositories, GitHub Actions can easily be incorporated with other services on GitHub: from bots, to branches, and to pull requests.&lt;/p&gt;

&lt;p&gt;This use case demonstrates the power of GitHub Actions beyond automating deployment; &lt;em&gt;extending into the area of learning and education&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>github</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Creating a File Sharing and Conversion Web App with Azure Functions</title>
      <dc:creator>Emily Chen</dc:creator>
      <pubDate>Sun, 20 Dec 2020 01:44:38 +0000</pubDate>
      <link>https://dev.to/bitproject/creating-a-file-sharing-and-conversion-web-app-with-azure-functions-7fm</link>
      <guid>https://dev.to/bitproject/creating-a-file-sharing-and-conversion-web-app-with-azure-functions-7fm</guid>
      <description>&lt;p&gt;If you or someone you know participated in this year's AP Collegeboard Exams, you probably recognize the stress of submitting handwritten work within a small time constraint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3o7TKRwpns23QMNNiE/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3o7TKRwpns23QMNNiE/giphy.gif" alt="https://media.giphy.com/media/3o7TKRwpns23QMNNiE/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bunnimage&lt;/strong&gt; aims to help alleviate that stress for students and others working at home. It takes an image as an input on an upload page and converts it into a PDF that is available at a download page. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Overview&lt;/strong&gt;
&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%2Fuser-images.githubusercontent.com%2F69332964%2F99191176-01198180-2739-11eb-9889-872822df6bd8.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%2Fuser-images.githubusercontent.com%2F69332964%2F99191176-01198180-2739-11eb-9889-872822df6bd8.png" alt="https://user-images.githubusercontent.com/69332964/99191176-01198180-2739-11eb-9889-872822df6bd8.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this tutorial, we'll be walking through:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating the "Upload" page and an HTTP Trigger Function that will upload the user's image to a storage container.&lt;/li&gt;
&lt;li&gt;Setting up an Event Grid Subscription and a Function that converts the image into a PDF and stores it again.

&lt;ul&gt;
&lt;li&gt;This is where the API will live!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Creating the "Download" page and an HTTP Trigger Function that retrieves the correct PDF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional&lt;/strong&gt; For those who are interested, we can add another Function to delete the files and keep our containers squeaky clean.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Note&lt;/strong&gt;: The diagram above excludes the optional deletion feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find a sample of the final product at &lt;a href="https://github.com/emsesc/bunnimage" rel="noopener noreferrer"&gt;my Github repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Before we start:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Make sure you have an &lt;strong&gt;&lt;a href="https://azure.microsoft.com/en-us/free/" rel="noopener noreferrer"&gt;Azure Subscription&lt;/a&gt;&lt;/strong&gt; so we can utilize the amazing features of Microsoft Azure Functions (It's free!) 🤩&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Register&lt;/strong&gt; for an account on &lt;strong&gt;&lt;a href="https://www.online-convert.com/register" rel="noopener noreferrer"&gt;Online Convert&lt;/a&gt;&lt;/strong&gt; (with the free version), as we will be using this API convert our images&lt;/li&gt;
&lt;li&gt;If you want to host your website somewhere, check out &lt;a href="https://repl.it/languages/html" rel="noopener noreferrer"&gt;Repl.it&lt;/a&gt;, or you can just have your project run &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" rel="noopener noreferrer"&gt;locally&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Upload the image ⬆️
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Creating a Function App&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We're going to have a lot of triggers in this project, so let's get started by &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function" rel="noopener noreferrer"&gt;creating a Function App&lt;/a&gt;! Follow those steps to create the Function App, and then create the first HTTP trigger (this will upload our image).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: it will be helpful if you keep track of these strings for reference later in the project: Storage account name (found in "Hosting"), Function app name, Resource Group&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before we start coding the trigger, though, we need to install some &lt;code&gt;npm&lt;/code&gt; packages/libraries.&lt;/p&gt;

&lt;p&gt;Click on the "Console" tab in the left panel under "Development Tools".&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%2Fuser-images.githubusercontent.com%2F69332964%2F99189070-59e31d00-272d-11eb-80a4-17444e5fac65.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%2Fuser-images.githubusercontent.com%2F69332964%2F99189070-59e31d00-272d-11eb-80a4-17444e5fac65.png" alt="https://user-images.githubusercontent.com/69332964/99189070-59e31d00-272d-11eb-80a4-17444e5fac65.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the console (shown on the right panel), type in the following commands:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm init -y&lt;/code&gt; &lt;br&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/parse-multipart" rel="noopener noreferrer"&gt;&lt;code&gt;npm install parse-multipart&lt;/code&gt;&lt;/a&gt; &lt;br&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/node-fetch" rel="noopener noreferrer"&gt;&lt;code&gt;npm install node-fetch&lt;/code&gt;&lt;/a&gt; &lt;br&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/@azure/storage-blob" rel="noopener noreferrer"&gt;&lt;code&gt;npm install @azure/storage-blob&lt;/code&gt;&lt;/a&gt; &lt;br&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: The Azure Storage Blob client library is going to be a key piece of the project. After all, it's about blobs!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Setting up your storage account&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is the storage account you created when creating the Function App. If you don't know what it is, search "Storage Containers" in the query box in Azure portal.&lt;/p&gt;

&lt;p&gt;We're going to need to create 2 containers: "images" and "pdfs." Think of these as folders in the account.&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%2Fuser-images.githubusercontent.com%2F69332964%2F99161767-75194280-26c3-11eb-8ad1-c19d63d37bbb.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%2Fuser-images.githubusercontent.com%2F69332964%2F99161767-75194280-26c3-11eb-8ad1-c19d63d37bbb.png" alt="https://user-images.githubusercontent.com/69332964/99161767-75194280-26c3-11eb-8ad1-c19d63d37bbb.png"&gt;&lt;/a&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%2Fuser-images.githubusercontent.com%2F69332964%2F99161780-8cf0c680-26c3-11eb-9bfc-78dc3262b038.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%2Fuser-images.githubusercontent.com%2F69332964%2F99161780-8cf0c680-26c3-11eb-9bfc-78dc3262b038.png" alt="https://user-images.githubusercontent.com/69332964/99161780-8cf0c680-26c3-11eb-9bfc-78dc3262b038.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will need to upgrade your storage account because Event Grid Subscriptions will only work with a v2 version. Follow this &lt;a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-account-upgrade?tabs=azure-portal" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; to upgrade it.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Writing our &lt;em&gt;First&lt;/em&gt; Azure Function to Upload an Image&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;⬇ &lt;strong&gt;Some housekeeping...&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the function to work, we have to initialize the packages/libraries we installed in the beginning of part 1.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Take note of the &lt;code&gt;process.env&lt;/code&gt; value being assigned to &lt;code&gt;connectionstring&lt;/code&gt; in the code below (&lt;em&gt;Line 3&lt;/em&gt;). Use this &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; to add in your own secret strings from your storage container.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The storage container is the one you created when you started your Function App. Navigate to it and find your secret strings here:&lt;/li&gt;
&lt;/ul&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%2Fuser-images.githubusercontent.com%2F69332964%2F99161798-ba3d7480-26c3-11eb-8e55-eac4bd4cb174.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%2Fuser-images.githubusercontent.com%2F69332964%2F99161798-ba3d7480-26c3-11eb-8e55-eac4bd4cb174.png" alt="https://user-images.githubusercontent.com/69332964/99161798-ba3d7480-26c3-11eb-8e55-eac4bd4cb174.png"&gt;&lt;/a&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%2Fuser-images.githubusercontent.com%2F69332964%2F99161822-ec4ed680-26c3-11eb-8977-f12beb496c24.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%2Fuser-images.githubusercontent.com%2F69332964%2F99161822-ec4ed680-26c3-11eb-8977-f12beb496c24.png" alt="https://user-images.githubusercontent.com/69332964/99161822-ec4ed680-26c3-11eb-8977-f12beb496c24.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep these safe, and use the connection string in the corresponding variable in the code.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Note: You'll need to store other strings in environment variables later on in the tutorial&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start just by initializing a few variables we'll need.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;⬇ &lt;strong&gt;The main block of code&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notice that we are able to name the file with the user's username in line 10 by receiving it from the header.

&lt;ul&gt;
&lt;li&gt;Later on in the JS, we will send the username in the header of the request.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The &lt;code&gt;parse-multipart&lt;/code&gt; library is being used in lines 4-11 to parse the image from the POST request we will later make with the frontend; refer to the documentation linked above.&lt;/li&gt;

&lt;li&gt;Some if-else logic is used from lines 13-22 to determine the file extension.&lt;/li&gt;

&lt;li&gt;We then call the &lt;code&gt;uploadBlob()&lt;/code&gt; function in line 24.&lt;/li&gt;

&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;⬇ &lt;strong&gt;Uploading the image blob to the "images" container&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notice the &lt;code&gt;uploadBlob()&lt;/code&gt; function! This is what uploads the parsed image to the specified "images" blob container.

&lt;ul&gt;
&lt;li&gt;Here's a &lt;a href="https://youtu.be/Qt_VXM_fml4" rel="noopener noreferrer"&gt;YouTube Video to help explain&lt;/a&gt; the handy dandy library&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Frontend: The "upload" webpage&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next, I created a static HTML page that will accept the image from the user and send to the Azure Function we just coded using Javascript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I removed unnecessary sections of my code because I wanted to make the webpage ✨&lt;em&gt;fancy&lt;/em&gt;✨, but you can see the whole thing &lt;a href="https://github.com/emsesc/bunnimage/blob/main/upload.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Above we have:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input box for the username (simple but &lt;em&gt;insecure&lt;/em&gt; auth system)&lt;/li&gt;
&lt;li&gt;Button to submit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, a static HTML webpage can't make a request to the Azure Function itself, which is where we're going to cook up some JS. 😯&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Frontend: Javascript for interacting with the Azure Function&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This block of Javascript updates the preview thumbnail while getting the picture, gets the username, and sends them both over to the function we just coded.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;loadFile()&lt;/code&gt; is called when the file input changes to display the thumbnail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;loadFile&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Got picture!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Get image from output &lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;// load inputted image into the image src and display&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, &lt;code&gt;handle()&lt;/code&gt; is called when the file is submitted to POST the image and username. The image is sent in the body, and username is sent as a header. &lt;em&gt;Lines 15-30&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Be sure to change the function url on Line 19 to the one of your upload image Function!&lt;/p&gt;
&lt;/blockquote&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%2Fuser-images.githubusercontent.com%2F69332964%2F99188529-73369a00-272a-11eb-93df-04fdce5381df.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%2Fuser-images.githubusercontent.com%2F69332964%2F99188529-73369a00-272a-11eb-93df-04fdce5381df.png" alt="https://user-images.githubusercontent.com/69332964/99188529-73369a00-272a-11eb-93df-04fdce5381df.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Deploy your code&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Try doing it locally with the &lt;strong&gt;live server extension&lt;/strong&gt; for VS Code&lt;/li&gt;
&lt;li&gt;Try &lt;a href="https://azure.microsoft.com/en-us/services/app-service/web/" rel="noopener noreferrer"&gt;Azure Web Apps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;I personally used &lt;a href="https://repl.it/languages/html" rel="noopener noreferrer"&gt;repl.it&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Update CORS Settings&lt;/strong&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a crucial step!! 😱 If you don't change your CORS (Cross-origin resource sharing) settings, the POST request won't work. This tells the Function App what domains can access our Azure Function.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Recommended&lt;/strong&gt;: Change it to a wildcard operator (&lt;code&gt;*&lt;/code&gt;), which allows &lt;em&gt;all&lt;/em&gt; origin domains to make requests&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Be sure to remove any other existing inputs before attempting to save with wildcard&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%2Fuser-images.githubusercontent.com%2F69332964%2F99188905-6f0b7c00-272c-11eb-8142-f91882227c78.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%2Fuser-images.githubusercontent.com%2F69332964%2F99188905-6f0b7c00-272c-11eb-8142-f91882227c78.png" alt="https://user-images.githubusercontent.com/69332964/99188905-6f0b7c00-272c-11eb-8142-f91882227c78.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Change it to the domain you are using to host your code&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Home stretch! 🏃🏻‍♀️&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;It's finally time to test our first step that our app will make!&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to your HTML page and submit an image&lt;/li&gt;
&lt;/ol&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%2Fuser-images.githubusercontent.com%2F69332964%2F99189240-3cfb1980-272e-11eb-8896-e959f37480b3.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%2Fuser-images.githubusercontent.com%2F69332964%2F99189240-3cfb1980-272e-11eb-8896-e959f37480b3.png" alt="https://user-images.githubusercontent.com/69332964/99189240-3cfb1980-272e-11eb-8896-e959f37480b3.png"&gt;&lt;/a&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%2Fuser-images.githubusercontent.com%2F69332964%2F99189255-53a17080-272e-11eb-9cab-a73faf504b3f.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%2Fuser-images.githubusercontent.com%2F69332964%2F99189255-53a17080-272e-11eb-9cab-a73faf504b3f.png" alt="https://user-images.githubusercontent.com/69332964/99189255-53a17080-272e-11eb-9cab-a73faf504b3f.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the "images" storage container and check to see if your image is there!&lt;br&gt;
&lt;em&gt;Error? Check the log in your Function&lt;/em&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%2Fuser-images.githubusercontent.com%2F69332964%2F99189316-9c592980-272e-11eb-9870-dbc1f9352599.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%2Fuser-images.githubusercontent.com%2F69332964%2F99189316-9c592980-272e-11eb-9870-dbc1f9352599.png" alt="https://user-images.githubusercontent.com/69332964/99189316-9c592980-272e-11eb-9870-dbc1f9352599.png"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Convert The Image 🔄
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Create another Azure Function&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Yep... We need yet &lt;em&gt;another&lt;/em&gt; Azure Function. (What can I say? They're pretty helpful.) This one will trigger when &lt;strong&gt;the image blob is stored&lt;/strong&gt;, then convert it into a PDF, and store it in the "pdfs" container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/QBGYWFjnggIZ8fMjdt/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/QBGYWFjnggIZ8fMjdt/giphy.gif" alt="https://media.giphy.com/media/QBGYWFjnggIZ8fMjdt/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, this time, it will be an &lt;strong&gt;Event Grid Trigger&lt;/strong&gt;, so make sure you select the right one!&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%2Fuser-images.githubusercontent.com%2F69332964%2F99191739-a4b86100-273c-11eb-8015-9988540fc67c.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%2Fuser-images.githubusercontent.com%2F69332964%2F99191739-a4b86100-273c-11eb-8015-9988540fc67c.png" alt="https://user-images.githubusercontent.com/69332964/99191739-a4b86100-273c-11eb-8015-9988540fc67c.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What's the difference?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event Grid Triggers trigger based on an Event Grid Subscription, which we will create later in this step.
Our trigger will fire when a blob is stored in the "images" container&lt;/li&gt;
&lt;li&gt;HTTP Triggers fire when a GET or POST request is made to the endpoint (function URL)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;strong&gt;Commercial Break&lt;/strong&gt; 📺&lt;/p&gt;

&lt;p&gt;Let's recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 1 ✅:&lt;/strong&gt; We created the "Upload" page and an HTTP Trigger Function that uploaded the user's image to a storage container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; We will create an &lt;strong&gt;Event Grid&lt;/strong&gt; function that converts the image into a PDF by calling the &lt;em&gt;Online Convert API&lt;/em&gt; and will upload the PDF to blob storage.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;⚠😵&lt;strong&gt;WARNING&lt;/strong&gt;😵⚠ Lots of code ahead, but it's all good! I split it into sections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First off, the &lt;em&gt;Online-Convert&lt;/em&gt; API!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're going to need to get another secret key, except this time from the API. Here's &lt;a href="https://apiv2.online-convert.com/docs/getting_started/api_key.html" rel="noopener noreferrer"&gt;how to get that&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Once again, save it in your environment variables so it's accessible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Note&lt;/strong&gt;: This API does have restrictions on the amount of conversions during 24 hours, so just be aware that you may get an error after reaching the limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⬇ This &lt;code&gt;convertImage()&lt;/code&gt; function does exactly what it's called: convert the image by calling the Online-Convert API. Here's some &lt;a href="https://apiv2.online-convert.com/docs/input_types/cloud/azure_blob_storage.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; around how to use the API with Azure Blob Storage.&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;convertImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blobName&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;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;convertAPI_KEY&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;accountKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accountKey&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;uriBase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;https://api2.online-convert.com/jobs&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// env variables (similar to .gitignore/.env file) to not expose personal info&lt;/span&gt;
    &lt;span class="c1"&gt;// check out documentation &lt;/span&gt;
    &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conversion&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&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="s2"&gt;pdf&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="s2"&gt;input&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="s2"&gt;cloud&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="s2"&gt;source&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="s2"&gt;azure&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="s2"&gt;parameters&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;container&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="s2"&gt;images&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="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blobName&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credentials&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accountname&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="s2"&gt;bunnimagestorage&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="s2"&gt;accountkey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountKey&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="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// making the post request&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uriBase&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// we want to send the image&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-oc-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api_key&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&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;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// receive the response&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&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;resp&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;return&lt;/span&gt; &lt;span class="nx"&gt;data&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;a href="https://apiv2.online-convert.com/docs/getting_started/job_polling.html" rel="noopener noreferrer"&gt;check the status of the conversion&lt;/a&gt; and determine whether we can store the PDF to blob storage yet, let's use this &lt;code&gt;checkStatus()&lt;/code&gt; function that makes a request to the same &lt;code&gt;https://api2.online-convert.com/jobs&lt;/code&gt; endpoint, except with a GET request instead of POST.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;checkStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobId&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;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;convertAPI_KEY&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;uriBase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;https://api2.online-convert.com/jobs&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// env variables to keep your info private!&lt;/span&gt;

    &lt;span class="c1"&gt;// making the post request&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uriBase&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;jobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/*The await expression causes async function execution to pause until a Promise is settled 
        (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment. 
        When resumed, the value of the await expression is that of the fulfilled Promise*/&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;GET&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;x-oc-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api_key&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="c1"&gt;// receive the response&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&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;resp&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;return&lt;/span&gt; &lt;span class="nx"&gt;data&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;Then we can use the same &lt;code&gt;uploadBlob()&lt;/code&gt; function from before to upload our object!&lt;/p&gt;

&lt;p&gt;After this we get to the main section of our code.&lt;/p&gt;

&lt;p&gt;⬇It gets the blobName, calls the functions, and downloads the PDF to be stored.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;blobName&lt;/code&gt; is retrieved from the &lt;code&gt;EventGrid&lt;/code&gt; subscription subject* in lines 10-11&lt;/li&gt;
&lt;li&gt;Because the API does not convert the image immediately, we need a while loop to repeatedly check for the status of the conversion in lines 21-36&lt;/li&gt;
&lt;li&gt;The last portion is used to &lt;a href="https://apiv2.online-convert.com/docs/getting_started/job_downloading.html" rel="noopener noreferrer"&gt;download the converted PDF&lt;/a&gt; by sending a GET request to the URI from the completed file conversion response. &lt;em&gt;Lines 43-47&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;What's a subject? This part of the Event response contains information about what specific file caused the Azure Function to fire. For example: &lt;code&gt;/blobServices/default/containers/test-container/blobs/new-file.txt&lt;/code&gt; where the file is &lt;code&gt;new-file.txt&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Now that the long block of code is done with, let's take a look at some responses you should expect from the API.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/emsesc/01e30ba32a6fd84572e35f26fe8479c4" rel="noopener noreferrer"&gt;This is&lt;/a&gt; what you would get if the file is still converting 🤔&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/emsesc/6018bafcf24f02a58a8f7f14d52ffbb8" rel="noopener noreferrer"&gt;Here's&lt;/a&gt; what you would get when the conversion is complete! (yay) 🥳&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In particular, there are 3 important pieces of the output we should examine:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;update.status.code&lt;/code&gt;: This tells us whether its done processing or not&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update.output[0].uri&lt;/code&gt;: This gives us the URL where we can download the PDF (used in the last GET request)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;result.id&lt;/code&gt;: Gives the ID of the file conversion "job" so we can continually check for its status&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before we can test our code, we need one last step: the trigger!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Creating an Event Subscription&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When the image blob is stored in the "images" container, we want the conversion from jpg/jpeg/png to pdf to begin &lt;em&gt;immediately&lt;/em&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Just like how "subscribing" to a YouTube channel gives you notifications, we're going to subscribe to our own Blob Storage and trigger the Azure Function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: You'll want to keep the names for your storage account and resource group handy.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search "Event Grid Subscriptions" in the search bar&lt;/li&gt;
&lt;li&gt;Click "+ Event Subscription" in the top left&lt;/li&gt;
&lt;li&gt;Fill in the form to create the Event Subscription:&lt;/li&gt;
&lt;/ol&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%2Fuser-images.githubusercontent.com%2F69332964%2F99469463-c10cf700-2910-11eb-929e-e7feff85f203.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%2Fuser-images.githubusercontent.com%2F69332964%2F99469463-c10cf700-2910-11eb-929e-e7feff85f203.png" alt="https://user-images.githubusercontent.com/69332964/99469463-c10cf700-2910-11eb-929e-e7feff85f203.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If it asks you for a name, feel free to put anything you want - I named it "fileUploaded"&lt;/li&gt;
&lt;li&gt;Under Topic Types, select "Storage Accounts"&lt;/li&gt;
&lt;li&gt;The "Resource Group" is the Resource Group that holds your storage account&lt;/li&gt;
&lt;li&gt;The "Resource" is your storage account name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If your storage account doesn't appear, you forgot to follow the "upgrade to v2 storage" step&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%2Fuser-images.githubusercontent.com%2F69332964%2F99469567-05989280-2911-11eb-9cf2-827a604f638e.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%2Fuser-images.githubusercontent.com%2F69332964%2F99469567-05989280-2911-11eb-9cf2-827a604f638e.png" alt="https://user-images.githubusercontent.com/69332964/99469567-05989280-2911-11eb-9cf2-827a604f638e.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under Event Types: filter to &lt;strong&gt;Blob Created&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&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%2Fuser-images.githubusercontent.com%2F69332964%2F99189740-aed46280-2730-11eb-8ff0-c8a7ba19aadc.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%2Fuser-images.githubusercontent.com%2F69332964%2F99189740-aed46280-2730-11eb-8ff0-c8a7ba19aadc.png" alt="https://user-images.githubusercontent.com/69332964/99189740-aed46280-2730-11eb-8ff0-c8a7ba19aadc.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "Endpoint Type" is "Azure Function"&lt;/li&gt;
&lt;/ul&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%2Fuser-images.githubusercontent.com%2F69332964%2F99189763-d0354e80-2730-11eb-91e4-5b17fc5e63bd.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%2Fuser-images.githubusercontent.com%2F69332964%2F99189763-d0354e80-2730-11eb-91e4-5b17fc5e63bd.png" alt="https://user-images.githubusercontent.com/69332964/99189763-d0354e80-2730-11eb-91e4-5b17fc5e63bd.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "Function" is the function we want triggered when an image is uploaded, so the &lt;code&gt;convertImage&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;Tweaking some settings...&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to the "Filters" tab and "Enable Subject Filtering"&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%2Fuser-images.githubusercontent.com%2F69332964%2F99189929-bd6f4980-2731-11eb-9b01-b0cef972b96a.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%2Fuser-images.githubusercontent.com%2F69332964%2F99189929-bd6f4980-2731-11eb-9b01-b0cef972b96a.png" alt="https://user-images.githubusercontent.com/69332964/99189929-bd6f4980-2731-11eb-9b01-b0cef972b96a.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Change the "Subject Begins With" to &lt;code&gt;/blobServices/default/containers/images/blobs/&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This way, the subscription will &lt;strong&gt;not&lt;/strong&gt; trigger when a PDF is stored in the "pdfs" container. It will &lt;strong&gt;only&lt;/strong&gt; trigger when something is stored in "images."&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Congratulations! You have now subscribed to the "blob created" event in your "images" container that triggers the convert image function!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Upload a converted PDF to the "pdfs" container!&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now that we've connected our Functions and frontend together with an Event Grid Subscription, try submitting another image to check if it successfully uploads as a PDF into the "pdfs" container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you used my code and have the same context.log()s, you should get something like this when the PDF uploads:&lt;/p&gt;
&lt;/blockquote&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%2Fuser-images.githubusercontent.com%2F69332964%2F99191696-50ad7c80-273c-11eb-947e-5e9a9962ddb0.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%2Fuser-images.githubusercontent.com%2F69332964%2F99191696-50ad7c80-273c-11eb-947e-5e9a9962ddb0.png" alt="https://user-images.githubusercontent.com/69332964/99191696-50ad7c80-273c-11eb-947e-5e9a9962ddb0.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Downloading the PDF on the HTML page ⬇
&lt;/h2&gt;

&lt;p&gt;Now that we have a PDF stored in the "pdfs" container, how will we get the PDF back to the user? &lt;strong&gt;You got it right, yet &lt;em&gt;another&lt;/em&gt; Azure Function&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Create another HTTP Trigger - this one will return the PDF download URL to the frontend when triggered.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/MYx9qA5yTknqgb4abz/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/MYx9qA5yTknqgb4abz/giphy.gif" alt="https://media.giphy.com/media/MYx9qA5yTknqgb4abz/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commercial Break&lt;/strong&gt; 📺&lt;/p&gt;

&lt;p&gt;Let's recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 1 ✅:&lt;/strong&gt; We created the "Upload" page and an HTTP Trigger Function that uploaded the user's image to a storage container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2 ✅:&lt;/strong&gt; We will create an &lt;strong&gt;Event Grid&lt;/strong&gt; function that converts the image into a PDF by calling the &lt;em&gt;Online Convert API&lt;/em&gt; and will upload the PDF to blob storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; We will create a HTTP Trigger function that returns the PDF to the user when triggered by the "Download" page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;Optional&lt;/em&gt;&lt;/strong&gt; If you choose, create another HTTP Trigger function and modify other code to delete the image and PDF blobs from storage containers once they are unneeded.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Azure Functions: Check if the PDF is ready to be served 🍝&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;⬇First, it receives the username to get the correct PDF from the header of the request, which is made by the webpage. You will see this request later on in the JS of this step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputBlob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JavaScript HTTP trigger function processed a request.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;https://bunnimagestorage.blob.core.windows.net/pdfs/&amp;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;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.pdf&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, using the personalized URL, it performs a GET request to check if the PDF has been stored in the "pdfs" container.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;download&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;GET&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&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;resp&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The specified blob does not exist.&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="nx"&gt;success&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Does not exist: &lt;/span&gt;&lt;span class="dl"&gt;"&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="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="nx"&gt;success&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Does exist: &lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;⬇The function then returns the URL for downloading the PDF and whether or not the PDF is ready for download to the webpage.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;downloadUri&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;success&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="c1"&gt;// receive the response&lt;/span&gt;

    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Frontend: Creating the Download HTML page&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once again, the "fancy" stuff is omitted.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;





&lt;p&gt;⬆&lt;strong&gt;Like we created the "upload" page in Step 1, we now need a "download" page for users to receive the PDF.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This piece of code creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An input for the username &lt;em&gt;Line 6&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;One button for refreshing to check if the PDF is ready &lt;em&gt;Line 8&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;One button for downloading the file &lt;em&gt;Line 9&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Frontend: Downloading the PDF on the Webpage&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Time to get bombarded with some &lt;em&gt;lovely&lt;/em&gt; JS!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1&lt;/strong&gt; ⬇:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the HTML on lines 2-4 to display the current status (whether it's looking for the PDF, whether it's ready for download, etc.)&lt;/li&gt;
&lt;li&gt;Make a request on lines 9-16 to the HTTP Trigger Function we just coded, sending the username inputted on the HTML page along with it&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Part 2&lt;/strong&gt; ⬇:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we're going to find the link to download the PDF with &lt;code&gt;data.downloadUri&lt;/code&gt; on &lt;em&gt;line 1&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Change buttons from "Refresh" to "Download" when PDF is ready for download

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How to do this?&lt;/strong&gt; Remove the "Refresh" button &lt;em&gt;Lines 10-11&lt;/em&gt; and make "Download" visible &lt;em&gt;Line 9&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Set the &lt;code&gt;onclick&lt;/code&gt; attribute of the "Download" button to call the &lt;code&gt;getPdf()&lt;/code&gt; function with the unique username + link for download. &lt;em&gt;Line 8&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;getPdf()&lt;/code&gt; function allows for immediate download with &lt;code&gt;window.open(link)&lt;/code&gt; &lt;em&gt;Lines 16-19&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Amazing! You're done!&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here's the finished product in which I download the cute bunny shopping picture I uploaded earlier.&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%2Fuser-images.githubusercontent.com%2F69332964%2F99192741-95d4ad00-2742-11eb-8b77-f0c9e6d159d7.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%2Fuser-images.githubusercontent.com%2F69332964%2F99192741-95d4ad00-2742-11eb-8b77-f0c9e6d159d7.png" alt="https://user-images.githubusercontent.com/69332964/99192741-95d4ad00-2742-11eb-8b77-f0c9e6d159d7.png"&gt;&lt;/a&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%2Fuser-images.githubusercontent.com%2F69332964%2F99192756-b00e8b00-2742-11eb-9fea-dc64a9083c63.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%2Fuser-images.githubusercontent.com%2F69332964%2F99192756-b00e8b00-2742-11eb-9fea-dc64a9083c63.png" alt="https://user-images.githubusercontent.com/69332964/99192756-b00e8b00-2742-11eb-9fea-dc64a9083c63.png"&gt;&lt;/a&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%2Fuser-images.githubusercontent.com%2F69332964%2F99192766-bbfa4d00-2742-11eb-8371-630af1b21778.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%2Fuser-images.githubusercontent.com%2F69332964%2F99192766-bbfa4d00-2742-11eb-8371-630af1b21778.png" alt="https://user-images.githubusercontent.com/69332964/99192766-bbfa4d00-2742-11eb-8371-630af1b21778.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! I hope this knowledge of Azure Functions helps you create even more fun apps!&lt;/p&gt;

&lt;p&gt;If you're interested in augmenting this app, try using your new knowledge of Blob Storage, HTTP Triggers, the Node SDK (@azure/storage-blob), and some &lt;a href="https://stackoverflow.com/questions/60716837/how-to-delete-a-blob-from-azure-blob-v12-sdk-for-node-js" rel="noopener noreferrer"&gt;Stack Overflow&lt;/a&gt; to assist you to add a feature to delete the image and PDF blobs.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>node</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
