<?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: Dan Wahlin</title>
    <description>The latest articles on DEV Community by Dan Wahlin (@danwahlin).</description>
    <link>https://dev.to/danwahlin</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%2F148030%2F55e946b9-62ab-4fb8-9918-21b9e738ed4d.jpeg</url>
      <title>DEV Community: Dan Wahlin</title>
      <link>https://dev.to/danwahlin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danwahlin"/>
    <language>en</language>
    <item>
      <title>AI Repo of the Week: Generative AI for Beginners with JavaScript</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Thu, 03 Jul 2025 11:08:07 +0000</pubDate>
      <link>https://dev.to/danwahlin/ai-repo-of-the-week-generative-ai-for-beginners-with-javascript-3e90</link>
      <guid>https://dev.to/danwahlin/ai-repo-of-the-week-generative-ai-for-beginners-with-javascript-3e90</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu143cax0vkmitew1aky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu143cax0vkmitew1aky.png" alt="Generative AI for Beginners with JavaScript" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A fun, hands-on learning journey that teaches JavaScript developers how to build AI-powered apps using generative AI and large language models.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Ready to explore the fascinating world of Generative AI using your JavaScript skills? This week’s featured repository, &lt;a href="https://github.com/microsoft/generative-ai-with-javascript" rel="noopener noreferrer"&gt;&lt;strong&gt;Generative AI for Beginners with JavaScript&lt;/strong&gt;&lt;/a&gt;, is your launchpad into the future of application development. Whether you’re just starting out or looking to expand your AI toolbox, this open-source GitHub resource offers a rich, hands-on journey. It includes interactive lessons, quizzes, and even time-travel storytelling featuring historical legends like Leonardo da Vinci and Ada Lovelace.&lt;/p&gt;

&lt;p&gt;Each chapter combines narrative-driven learning with practical exercises, helping you understand foundational AI concepts and apply them directly in code. It’s immersive, educational, and genuinely fun.  &lt;/p&gt;

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

&lt;h2&gt;
  
  
  What You’ll Learn
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 🧠 Foundations of Generative AI and LLMs
&lt;/h3&gt;

&lt;p&gt;Start with the basics: What is generative AI? How do large language models (LLMs) work? This chapter lays the groundwork for how these technologies are transforming JavaScript development.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 🚀 Build Your First AI-Powered App
&lt;/h3&gt;

&lt;p&gt;Walk through setting up your environment and creating your first AI app. Learn how to configure prompts and unlock the potential of AI in your own projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 🎯 Prompt Engineering Essentials
&lt;/h3&gt;

&lt;p&gt;Get hands-on with prompt engineering techniques that shape how AI models respond. Explore strategies for crafting prompts that are clear, targeted, and effective.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 📦 Structured Output with JSON
&lt;/h3&gt;

&lt;p&gt;Learn how to guide the model to return structured data formats like JSON—critical for integrating AI into real-world applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 🔍 Retrieval-Augmented Generation (RAG)
&lt;/h3&gt;

&lt;p&gt;Go beyond static prompts by combining LLMs with external data sources. Discover how RAG lets your app pull in live, contextual information for more intelligent results.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. 🛠️ Function Calling and Tool Use
&lt;/h3&gt;

&lt;p&gt;Give your LLM new powers! Learn how to connect your own functions and tools to your app, enabling more dynamic and actionable AI interactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. 📚 Model Context Protocol (MCP)
&lt;/h3&gt;

&lt;p&gt;Dive into MCP, a new standard for organizing prompts, tools, and resources. Learn how it simplifies AI app development and fosters consistency across projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. ⚙️ Enhancing MCP Clients with LLMs
&lt;/h3&gt;

&lt;p&gt;Build on what you’ve learned by integrating LLMs directly into your MCP clients. See how to make them smarter, faster, and more helpful.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;More chapters coming soon—watch the &lt;a href="https://github.com/microsoft/generative-ai-with-javascript" rel="noopener noreferrer"&gt;repo&lt;/a&gt; for updates!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Companion App: Interact with History
&lt;/h2&gt;

&lt;p&gt;Experience the power of generative AI in action through the companion web app—where you can chat with historical figures and witness how JavaScript brings AI to life in real time. &lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Talk with Dinocrates and other historical characters using a custom AI app!&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/microsoft/generative-ai-with-javascript" rel="noopener noreferrer"&gt;&lt;strong&gt;Generative AI for Beginners with JavaScript&lt;/strong&gt;&lt;/a&gt; is more than a course—it’s an adventure into how storytelling, coding, and AI can come together to create something fun and educational. Whether you’re here to upskill, experiment, or build the next big thing, this repository is your all-in-one resource to get started with confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jump into the future of development—&lt;a href="https://github.com/microsoft/generative-ai-with-javascript" rel="noopener noreferrer"&gt;check out the repo&lt;/a&gt; and start building with AI today!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>azure</category>
      <category>genai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>AI Repo of the Week: MCP for Beginners</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Fri, 23 May 2025 05:51:00 +0000</pubDate>
      <link>https://dev.to/danwahlin/ai-repo-of-the-week-mcp-for-beginners-42ab</link>
      <guid>https://dev.to/danwahlin/ai-repo-of-the-week-mcp-for-beginners-42ab</guid>
      <description>&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/05/image-5.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q6zss7xbe10lbwsnq33.png" width="638" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Welcome to this week’s spotlight in our AI Repo of the Week series—introducing the &lt;a href="https://github.com/microsoft/mcp-for-beginners" rel="noopener noreferrer"&gt;MCP for Beginners course&lt;/a&gt;, a fantastic starting point for AI developers and enthusiasts eager to navigate the world of Model Context Protocol (MCP). This open-source GitHub repository offers an in-depth exploration of MCP, an interface designed to extend the capabilities of AI models by integrating them with external tools, APIs, and data sources. Whether you’re new to MCP or looking to strengthen your understanding, this comprehensive resource is a perfect fit.&lt;/p&gt;

&lt;p&gt;The repository provides practical lessons and examples that guide developers through creating MCP servers in multiple programming environments, including C#, Python, Java, and TypeScript. A primary goal is to demystify the process of building scalable AI applications by employing standardized protocols. Here’s a quick overview of what you can expect from this repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Key Features &amp;amp; Learning Journey&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Getting Started with MCP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The “MCP for Beginners” repository kicks off with an introduction to the Model Context Protocol, setting the stage by explaining why a standardized approach matters for scalable AI applications. Users can quickly get their hands dirty with step-by-step lessons designed to implement basic server functionality. This practical approach ensures that you not only learn the MCP theory but also see it in action. The repository is structured to facilitate a smooth learning curve, with each section building on the previous one. Here are a few of the key features you’ll learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core Concepts Explained&lt;/strong&gt; : In-depth exploration of the core concepts of MCP, including client-server architecture, key protocol components, and messaging patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First Server Setup&lt;/strong&gt; : Begin by creating your first MCP server. You’ll explore how to utilize tools like the inspector to test and debug your setups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Development&lt;/strong&gt; : Progress to building a client that can interact with the MCP server. Special attention is given to enhancing client capabilities using Large Language Models (LLMs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Server Integration&lt;/strong&gt; : Learn to operate MCP servers in various environments, including consumption via GitHub Copilot Agent and Visual Studio Code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the complete MCP curriculum structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/00-Introduction/README.md" rel="noopener noreferrer"&gt;Introduction to MCP&lt;/a&gt;&lt;/strong&gt;
Overview of the Model Context Protocol and its significance in AI pipelines, including what is the Model Context Protocol, why standardization matters and practical use cases and benefits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/01-CoreConcepts/README.md" rel="noopener noreferrer"&gt;Core Concepts Explained&lt;/a&gt;&lt;/strong&gt;
In-depth exploration of the core concepts of MCP, including client-server architecture, key protocol components, and messaging patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/02-Security/README.md" rel="noopener noreferrer"&gt;Security in MCP&lt;/a&gt;&lt;/strong&gt;
Identifying security threats within MCP-based systems, techniques and best practices for securing implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/README.md" rel="noopener noreferrer"&gt;Getting Started with MCP&lt;/a&gt;&lt;/strong&gt;
Environment setup and configuration, creating basic MCP servers and clients, integrating MCP with existing applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/01-first-server/README.md" rel="noopener noreferrer"&gt;First server&lt;/a&gt;&lt;/strong&gt;
Setting up a basic server using the MCP protocol, understanding the server-client interaction, and testing the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/02-client/README.md" rel="noopener noreferrer"&gt;First client&lt;/a&gt;&lt;/strong&gt;
Setting up a basic client using the MCP protocol, understanding the client-server interaction, and testing the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/03-llm-client/README.md" rel="noopener noreferrer"&gt;Client with LLM&lt;/a&gt;&lt;/strong&gt;
Setting up a client using the MCP protocol with a Large Language Model (LLM).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/04-vscode/README.md" rel="noopener noreferrer"&gt;Consuming a server with Visual Studio Code&lt;/a&gt;&lt;/strong&gt;
Setting up Visual Studio Code to consume servers using the MCP protocol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/05-sse-server/README.md" rel="noopener noreferrer"&gt;Creating a server using SSE&lt;/a&gt;&lt;/strong&gt;
SSE helps expose a server to the internet. This section will help you create a server using SSE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/06-aitk/README.md" rel="noopener noreferrer"&gt;Use AI Toolkit&lt;/a&gt;&lt;/strong&gt;
AI Toolkit is a great tool that will help you manage your AI and MCP workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/07-testing/README.md" rel="noopener noreferrer"&gt;Testing your server&lt;/a&gt;&lt;/strong&gt;
Testing is an important part of the development process. This section will help you test using several different tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/08-deployment/README.md" rel="noopener noreferrer"&gt;Deploy your server&lt;/a&gt;&lt;/strong&gt;
How do you go from local development to production? This section will help you develop and deploy your server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/04-PracticalImplementation/README.md" rel="noopener noreferrer"&gt;Practical Implementation&lt;/a&gt;&lt;/strong&gt;
Using SDKs across different languages, debugging, testing, and validation, crafting reusable prompt templates and workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/05-AdvancedTopics/README.md" rel="noopener noreferrer"&gt;Advanced Topics in MCP&lt;/a&gt;&lt;/strong&gt;
Multi-modal AI workflows and extensibility, secure scaling strategies, MCP in enterprise ecosystems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/06-CommunityContributions/README.md" rel="noopener noreferrer"&gt;Community Contributions&lt;/a&gt;&lt;/strong&gt;
How to contribute code and docs, collaborating via GitHub, community-driven enhancements and feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/07-LessonsFromEarlyAdoption/README.md" rel="noopener noreferrer"&gt;Insights from Early Adoption&lt;/a&gt;&lt;/strong&gt;
Real-world implementations and what worked, building and deploying MCP-based solutions, trends and future roadmap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/08-BestPractices/README.md" rel="noopener noreferrer"&gt;Best Practices for MCP&lt;/a&gt;&lt;/strong&gt;
Performance tuning and optimization, designing fault-tolerant MCP systems, testing and resilience strategies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners/blob/main/09-CaseStudy/README.md" rel="noopener noreferrer"&gt;MCP Case Studies&lt;/a&gt;&lt;/strong&gt;
Deep-dives into MCP solution architectures, deployment blueprints and integration tips, annotated diagrams and project walkthroughs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Practical Implementation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Throughout the curriculum, you’ll explore key MCP concepts alongside diverse, hands-on samples that demonstrate practical applications in multiple programming languages. For example, a simple calculator server illustrates how MCP principles can be applied to build API endpoints for operations like addition, subtraction, multiplication, and division, complete with type safety and error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Advanced Concepts&lt;/strong&gt; and Case Study
&lt;/h2&gt;

&lt;p&gt;In addition to covering MCP fundamentals, the course will also walk you through more advanced topics as well. This includes learning about multi-modal integrations, highlighting how AI models can expand beyond text-only capabilities to include data types like images and audio. You’ll also learn about scalable MCP architectures, security, performance optimization, and more.&lt;/p&gt;

&lt;p&gt;The course also walks you through an &lt;a href="https://github.com/Azure-Samples/azure-ai-travel-agents" rel="noopener noreferrer"&gt;Azure AI Travel Agents&lt;/a&gt; case study that demonstrates using MCP in an AI agents scenario.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/05/image-6-scaled.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80y8n0haa92zcpjv6a9h.png" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/microsoft/mcp-for-beginners" rel="noopener noreferrer"&gt;MCP for Beginners&lt;/a&gt; is a great resource for anyone journeying into the realm of AI and MCP development. It provides developers with the necessary knowledge and tools to create flexible and scalable AI applications that can integrate with a variety of data sources.   &lt;/p&gt;

&lt;p&gt;Dive into the repository, experiment with the provided examples and tools, and quickly get up-to-speed on what MCP is and how you can begin using it. Visit the &lt;a href="https://github.com/microsoft/mcp-for-beginners" rel="noopener noreferrer"&gt;MCP for Beginners repository on GitHub&lt;/a&gt;, star it, and help drive forward the mission of better, standardized AI integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional AI Courses
&lt;/h2&gt;

&lt;p&gt;In addition to the MCP for Beginners course, check out the following courses as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/ai-agents-for-beginners?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;AI Agents For Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/Generative-AI-for-beginners-dotnet?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;Generative AI for Beginners using .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/generative-ai-with-javascript" rel="noopener noreferrer"&gt;Generative AI for Beginners using JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/generative-ai-for-beginners?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;Generative AI for Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aka.ms/ml-beginners?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;ML for Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aka.ms/datascience-beginners?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;Data Science for Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aka.ms/ai-beginners?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;AI for Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aka.ms/GitHubCopilotAI?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;Mastering GitHub Copilot for AI Paired Programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/mastering-github-copilot-for-dotnet-csharp-developers?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;Mastering GitHub Copilot for C#/.NET Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/CopilotAdventures?WT.mc_id=academic-105485-koreyst" rel="noopener noreferrer"&gt;Choose Your Own Copilot Adventure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>course</category>
      <category>mcp</category>
    </item>
    <item>
      <title>AI Repo of the Week: GitHub Copilot Adventures</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Sat, 03 May 2025 23:49:24 +0000</pubDate>
      <link>https://dev.to/danwahlin/ai-repo-of-the-week-github-copilot-adventures-4b6h</link>
      <guid>https://dev.to/danwahlin/ai-repo-of-the-week-github-copilot-adventures-4b6h</guid>
      <description>&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/05/image.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxbyi862phhlnc990fume.png" alt="GitHub Copilot Adventures" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Welcome to the AI Repo of the Week series! In this first post we’re going to look at the &lt;a href="https://github.com/microsoft/CopilotAdventures" rel="noopener noreferrer"&gt;GitHub Copilot Adventures repo&lt;/a&gt;. Stay tuned for additional posts that cover AI-related repos across multiple languages and technologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Copilot Adventures
&lt;/h2&gt;

&lt;p&gt;Kickstart your GitHub Copilot journey with &lt;a href="https://github.com/microsoft/CopilotAdventures" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub&lt;/strong&gt;  &lt;strong&gt;Copilot Adventures&lt;/strong&gt;&lt;/a&gt;, a hands-on playground of “choose-your-own-adventure” coding challenges. In just a few minutes, you’ll go from predicting the next number in a sequence to simulating interstellar alignments, all guided by GitHub Copilot’s AI suggestions. Whether you’re brand new to Copilot or looking to level up your AI-paired programming skills, this repo offers step-by-step scenarios in multiple languages.&lt;/p&gt;

&lt;p&gt;Each adventure is packaged as a story—complete with context, objectives, and high-level tasks—so you can dive straight into the code. Behind the scenes, a &lt;strong&gt;Solutions&lt;/strong&gt; directory provides reference implementations in C#, JavaScript, and Python, just in case you get stuck and need a little assistance. By the end of your first run-through, you’ll have explored everything from console apps to HTTP calls, regex-powered text extraction, data structures, and even grid-based battle simulations—all with GitHub Copilot as your AI pair programmer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; GitHub Copilot provides &lt;a href="https://github.blog/ai-and-ml/copilot-ask-edit-and-agent-modes-what-they-do-and-when-to-use-them/" rel="noopener noreferrer"&gt;Ask, Edit, and Agent modes&lt;/a&gt;. The adventures in this repo rely on what is referred to as “Ask mode” where you prompt the AI for code suggestions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Your Own Adventure
&lt;/h2&gt;

&lt;p&gt;Several “adventures” are provided in the repo from beginner to advanced. Brand new to GitHub Copilot? Start with the warm-up adventure which will introduce you to some of the core concepts you need to know to get started. From there, you can jump into any of the beginner, intermediate, or advanced adventures and create them using your chosen language. As mentioned, solutions are provided for C#, JavaScript, and Python in case you get stuck and need a little help.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warm-Up Adventure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/05/image-1.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6sqvs0u58ggjrs2q4hav.png" alt="Warm-Up Adventure" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start here if need a quick introduction to GitHub Copilot, what it is, and how you can get started using it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chamber of Echoes&lt;/strong&gt; : Predict the next number in an arithmetic sequence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Beginner Adventures&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/05/image-2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flkazrnh0xn6g3pazsyig.png" alt="Beginner Adventures" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Clockwork Town of Tempora&lt;/strong&gt; : Calculate clock drift in minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Magical Forest of Algora&lt;/strong&gt; : Simulate dance moves to create magical effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Intermediate Adventures&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/05/image-3.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyj2rd2p5i8g2ubb8xnw.png" alt="Intermediate Adventures" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Celestial Alignment of Lumoria&lt;/strong&gt; : Determine planetary light intensity based on shadows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Legendary Duel of Stonevale&lt;/strong&gt; : Simulate a rock-paper-scissors duel with weighted scoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Scrolls of Eldoria&lt;/strong&gt; : Fetch and filter secrets using regex from a remote text file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advanced Adventures&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/05/image-4.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpoc69r7n31eq94tqlvbp.png" alt="Advanced Adventures" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Gridlock Arena of Mythos&lt;/strong&gt; : Build a turn-based grid battle with overlapping moves and score tracking.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;By tackling each adventure—from a simple echo chamber to a full gridlock arena—you’ll not only learn core programming concepts across languages but also see firsthand how GitHub Copilot accelerates development, improves code quality, and surfaces best practices to increase your productivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Take the next step!&lt;/strong&gt; Explore the full adventure library, run the code locally (or create your own GitHub Codespace to get started super fast), and unleash the power of AI-paired coding today. &lt;a href="https://github.com/microsoft/CopilotAdventures" rel="noopener noreferrer"&gt;Visit the Copilot Adventures repo on GitHub&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>Building a Marvel MCP Server with External APIs</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Tue, 01 Apr 2025 07:21:03 +0000</pubDate>
      <link>https://dev.to/danwahlin/building-a-marvel-mcp-server-with-external-apis-53pi</link>
      <guid>https://dev.to/danwahlin/building-a-marvel-mcp-server-with-external-apis-53pi</guid>
      <description>&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/03/captain-america.jpg" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmln3j282uo1om9lfu7q.jpg" alt="Captain America" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image from the Marvel Developers API&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP enables AI models to access external APIs through standardized tool calls, supercharging their capabilities with real-time data and actions.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/DanWahlin/marvel-mcp" rel="noopener noreferrer"&gt;Marvel MCP server&lt;/a&gt; provides “tools” for interacting with the &lt;a href="https://developer.marvel.com" rel="noopener noreferrer"&gt;Marvel API&lt;/a&gt;, which is useful for fetching character and comic data and integrating it with AI systems. Even if you’re not coding for the Avengers, it’s a great way to learn MCP integration and create an MCP server for your own APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction to Model Context Protocol (MCP)
&lt;/h2&gt;

&lt;p&gt;Ever wish your AI assistant could tap into external APIs, fetch live data, or interact with tools beyond its built-in knowledge? That’s exactly what the Model Context Protocol (MCP) enables. MCP is a standardized way for AI systems to discover available tools, send them inputs, and get back results in a consistent format. It’s a powerful way to extend what AI can do—especially for things like making API calls or querying databases, which models can’t do on their own. Think of it as the Rosetta Stone between AI models and the outside world.&lt;/p&gt;

&lt;p&gt;To make this real (and fun), I put together a Marvel-themed MCP server. It acts as a bridge to the &lt;a href="https://developer.marvel.com" rel="noopener noreferrer"&gt;Marvel API&lt;/a&gt;, letting you pull in data about characters, comics, and more—perfect for giving AI apps some superhero flair.&lt;/p&gt;

&lt;p&gt;Now, I realize that unless you’re working at Stark Industries, you probably won’t be using Marvel data in your day job. But hey, capes or not, this project gives you a practical blueprint for building your own API-connected MCP server that works with tools like &lt;a href="https://claude.ai/download" rel="noopener noreferrer"&gt;Claude Desktop&lt;/a&gt; or &lt;a href="https://code.visualstudio.com" rel="noopener noreferrer"&gt;GitHub Copilot in VS Code&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;I originally built the &lt;a href="https://github.com/danwahlin/marvel-mcp" rel="noopener noreferrer"&gt;Marvel MCP server project&lt;/a&gt; to experiment with integrating API data into AI systems. I needed some realistic APIs to use and the Marvel APIs worked really well. They’re rich, fun to explore, and perfect for a demo that’s a little more exciting than pulling weather data.  &lt;/p&gt;

&lt;p&gt;The project’s structure includes several key files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;src/index.ts&lt;/strong&gt; : Sets up the MCP stdio server and handles requests for listing and calling tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/tools/tools.ts&lt;/strong&gt; : Defines MCP tools used to interact with the Marvel API, each with a description, input schema, and handler function. Each tool is in its own folder and defines the schemas it needs and the overall functionality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/tools/schemas.ts&lt;/strong&gt; : Contains shared &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod schemas&lt;/a&gt; used by tools that call the Marvel API. The schemas are generated from the Open API spec available from &lt;a href="https://gateway.marvel.com/docs/public" rel="noopener noreferrer"&gt;https://gateway.marvel.com/docs/public&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/utils.ts&lt;/strong&gt; : Provides utility functions used by the MCP server tools to handle authentication (via public and private keys that the Marvel Developer API provides) and HTTP requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/instructions.ts:&lt;/strong&gt; Provides general prompts used across all tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/server.ts&lt;/strong&gt; and &lt;strong&gt;src/streamable-http.ts:&lt;/strong&gt; The MCP server can be run as stdio (that’s the default option to use it in an MCP host – more on this later). However, these two files enable running the new streamable HTTP option available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s walk through several of these files and highlight key aspects that are used to create the MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of Schemas
&lt;/h2&gt;

&lt;p&gt;APIs can have input parameters and return a specific type of response. MCP tools need to know what to pass to an API endpoint and the type of response to exact. That’s where schemas come into play. Each tool folder such as &lt;strong&gt;tools/get_characters&lt;/strong&gt; has the schemas used by the tool and well as the functionality to call the target API endpoint. Schemas define input parameters passed from the tool to the API (filters, IDs, etc.) as well as the shape of the response (ID, name, description, etc.). Schemas rely on the &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod npm package&lt;/a&gt; to define an object’s structure and data types.&lt;/p&gt;

&lt;p&gt;Here’s an example of how Zod is used to create &lt;strong&gt;GetCharactersSchema&lt;/strong&gt; which is located in the &lt;strong&gt;tools/get_characters/schemas.ts&lt;/strong&gt; file.&lt;/p&gt;

&lt;h5&gt;
  
  
  CharactersSchema
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GetCharactersSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;nameStartsWith&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;modifiedSince&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;comics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;stories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&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 schema defines the input parameters used to fetch characters from the API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optional filters like name, nameStartsWith, and modifiedSince for searching and date filtering.&lt;/li&gt;
&lt;li&gt;Optional comma-separated lists for comics, series, events, and stories to filter by related entities.&lt;/li&gt;
&lt;li&gt;orderBy for sorting results, and limit and offset for pagination, with limit constrained between 1 and 100.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to input schemas, there are also output schemas as mentioned earlier. Here’s an example of &lt;strong&gt;CharacterSchema&lt;/strong&gt; which represents the shape of the data in the API response. Because it’s used across multiple tools, it’s defined in &lt;strong&gt;tools/schemas.ts&lt;/strong&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  CharacterSchema
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CharacterSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;modified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;resourceURI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UrlSchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ImageSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;comics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ListSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ComicSummarySchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;stories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ListSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;StorySummarySchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ListSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EventSummarySchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ListSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SeriesSummarySchema&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 schema defines a Marvel character, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;id&lt;/strong&gt; : A numeric identifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt; : The character’s name as a string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;description&lt;/strong&gt; : A character description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;modified&lt;/strong&gt; : A string representing the last modification date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;resourceURI&lt;/strong&gt; : The canonical URL for the character.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;urls&lt;/strong&gt; : An array of UrlSchema objects, likely for external links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;thumbnail&lt;/strong&gt; : An ImageSchema for the character’s image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;comics, stories, events, series&lt;/strong&gt; : Lists with respective summary schemas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This schema ensures that any character data received from an API call conforms to the schema structure, which helps an AI system better understand the data it receives.&lt;/p&gt;

&lt;p&gt;Creating schemas by hand is really tedious, so I used &lt;a href="https://grok.com/chat" rel="noopener noreferrer"&gt;Grok 3&lt;/a&gt; to convert portions of Marvel’s &lt;a href="https://gateway.marvel.com/docs/public" rel="noopener noreferrer"&gt;OpenAPI spec&lt;/a&gt; into the desired schemas. GitHub Copilot, ChatGPT, and others should be able to handle the conversion for you as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of Tools
&lt;/h2&gt;

&lt;p&gt;One of the key features of MCP servers is &lt;a href="https://modelcontextprotocol.io/docs/concepts/tools" rel="noopener noreferrer"&gt;tools&lt;/a&gt;. Tools allow an LLM used within an AI system to perform actions through the MCP server. Think of tools as buttons the AI can press to go do something useful—like asking the Marvel API for data.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;src/tools/tools.ts&lt;/strong&gt; file defines several tools for the Marvel MCP server, each with a description, schema, and handler. Here’s an example of the &lt;strong&gt;get_characters&lt;/strong&gt; tool used to call the Marvel API.&lt;/p&gt;

&lt;h5&gt;
  
  
  get_characters Tool
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;get_characters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Fetch Marvel characters with optional filters.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetCharactersSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;argsParsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GetCharactersSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&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;res&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;httpRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/characters&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;serializeQueryParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;argsParsed&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;CharacterDataWrapperSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt; : Fetches Marvel characters, allowing optional filters like name, modification date, and related entities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt; : Uses GetCharactersSchema, ensuring inputs are validated against expected parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handler&lt;/strong&gt; : Parses the input using GetCharactersSchema, serializes query parameters using serializeQueryParams from utils.ts, makes an HTTP request to Marvel’s /characters API using httpRequest (also in utils.ts), and parses the response with CharacterDataWrapperSchema. This ensures the response conforms to the expected character data structure and helps the AI system understand the data it receives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another tool is &lt;em&gt;get_comics_for_character&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;get_comics_for_character&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Fetch Marvel comics filtered by character ID and optional filters.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetComicsForCharacterSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;characterId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GetComicsForCharacterSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&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;res&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;httpRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/characters/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;characterId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/comics`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;serializeQueryParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rest&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;ComicDataWrapperSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt; : Fetches comics featuring a specific character, with additional optional filters like format, date range, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt; : Uses GetComicsForCharacterSchema, ensuring input validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handler&lt;/strong&gt; : Parses the input, extracts characterId and other parameters, makes a request to /characters/{characterId}/comics with serialized query parameters, and parses the response with ComicDataWrapperSchema, ensuring comic data integrity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;src/tools/tools.ts&lt;/strong&gt; file aggregates all of the available tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;get_characters&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;./get_characters/index.js&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;get_character_by_id&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;./get_character_by_id/index.js&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;get_comics_for_character&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;./get_comics_for_character/index.js&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;get_comics&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;./get_comics/index.js&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;get_comic_by_id&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;./get_comic_by_id/index.js&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;get_characters_for_comic&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;./get_characters_for_comic/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;marvelTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;get_characters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;get_character_by_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;get_comics_for_character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;get_comics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;get_comic_by_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;get_characters_for_comic&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ToolName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;marvelTools&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tools demonstrate how the server abstracts Marvel API calls, providing a standardized interface for MCP interactions. The input schemas help the AI system understand what data it needs to pass and the output schemas help the AI system’s LLM understand the data it recevied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Utility Functions
&lt;/h2&gt;

&lt;p&gt;Looking at the previous tools, you may have noticed that an &lt;strong&gt;httpRequest()&lt;/strong&gt; function handles the calls to the Marvel API endpoints. It’s located in &lt;strong&gt;src/utils.ts&lt;/strong&gt;. Here’s what the function looks like.&lt;/p&gt;

&lt;h5&gt;
  
  
  httpRequest()
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&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;httpRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&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;const&lt;/span&gt; &lt;span class="nx"&gt;url&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MARVEL_API_BASE&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;endpoint&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;const&lt;/span&gt; &lt;span class="nx"&gt;authParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAuthParams&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;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&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;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apikey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apikey&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;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&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="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;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&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;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&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;text&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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="s2"&gt;`Marvel API error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;text&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt; : Makes HTTP requests to the Marvel API, handling authentication with timestamp, API key, and hash, and ensuring error handling for non-OK responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage&lt;/strong&gt; : Called by tool handlers to fetch data, ensuring secure and reliable API interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The MCP Server
&lt;/h2&gt;

&lt;p&gt;MCP servers can support a variety of transport types including stdio, SSE, and HTTP streaming (the newest option). Think of your MCP server quietly listening like Jarvis in the background, ready to take orders via stdin and reply via stdout as an AI system needs more information. The Marvel MCP server uses &lt;em&gt;stdio&lt;/em&gt; by default since it’s currently supported by most MCP hosts such as Claude Desktop and GitHub Copilot in VS Code. However, the project also supports the &lt;a href="https://modelcontextprotocol.io/specification/draft/basic/transports#streamable-http" rel="noopener noreferrer"&gt;streamable HTTP transport&lt;/a&gt;as mentioned earlier.  &lt;/p&gt;

&lt;p&gt;An MCP server running with &lt;code&gt;stdio&lt;/code&gt; acts like a background process that listens for JSON-formatted tool calls via standard input, processes the requests (like calling an API or performing a task), and then returns the results via standard output.   &lt;/p&gt;

&lt;p&gt;Here’s some of the key code in the Marvel MCP server’s &lt;strong&gt;src/index.ts&lt;/strong&gt; file including how the server is initialized, how tools are exposed to MCP clients, and how individual tools are invoked.&lt;/p&gt;

&lt;h5&gt;
  
  
  Initializing the MCP Server
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Server&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;@modelcontextprotocol/sdk/server/index.js&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;StdioServerTransport&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;@modelcontextprotocol/sdk/server/stdio.js&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;CallToolRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ListToolsRequestSchema&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;@modelcontextprotocol/sdk/types.js&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;zodToJsonSchema&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;zod-to-json-schema&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;marvelTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToolName&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;./tools.js&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;const&lt;/span&gt; &lt;span class="nx"&gt;server&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;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marvel-mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.5.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An MCP Server to retrieve Marvel character information.&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="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;tools&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Initializes the MCP server with metadata, including name, version, and description, and specifies that the server supports tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  List Tools Request Handler
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ListToolsRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;marvelTools&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tool&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;zodToJsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&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;ul&gt;
&lt;li&gt;Handles requests to list tools, mapping each tool from &lt;strong&gt;marvelTools&lt;/strong&gt; (shown earlier) to include name, description, and input schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Call Tool Request Handler
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CallToolRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="o"&gt;=&amp;gt;&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Processing tool request: &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;name&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;arguments&lt;/span&gt;&lt;span class="p"&gt;)&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;Arguments are required&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;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;marvelTools&lt;/span&gt;&lt;span class="p"&gt;))&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="s2"&gt;`Unknown tool: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&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="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;marvelTools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ToolName&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`Tool not found: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&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;result&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;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Completed tool request: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&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;result&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="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="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;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`Error processing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&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;ul&gt;
&lt;li&gt;Handles requests by an AI system to call a specific tool, validating the input, checking if the tool exists, executing the handler, and returning the result as JSON.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup ensures the server can respond to MCP protocol requests, providing a robust interface for tool interactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the MCP Server in an MCP Host
&lt;/h2&gt;

&lt;p&gt;Now that you’ve got your MCP server built, how do you connect it to something like Claude or Copilot? It’s easier than you might think. Just a bit of configuration and you’re off to the races.&lt;/p&gt;

&lt;p&gt;For example, to use the Marvel MCP server with &lt;strong&gt;Claude Desktop&lt;/strong&gt; you can add the following JSON into the &lt;strong&gt;claude_desktop_config.json&lt;/strong&gt; file.&lt;/p&gt;

&lt;h5&gt;
  
  
  Claude Desktop MCP Server Configuration
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&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;"mcpServers"&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;"marvel-mcp"&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;"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;"stdio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"@codewithdan/marvel-mcp"&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;"env"&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;"MARVEL_PUBLIC_KEY"&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_PUBLIC_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MARVEL_PRIVATE_KEY"&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_PRIVATE_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MARVEL_API_BASE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gateway.marvel.com/v1/public"&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;/div&gt;



&lt;p&gt;To use the server with &lt;strong&gt;GitHub Copilot in VS Code&lt;/strong&gt; you can add the following section to your settings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code MCP Server Configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"mcp"&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;"servers"&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;"marvel-mcp"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="s2"&gt;"@codewithdan/marvel-mcp"&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;"env"&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;"MARVEL_PUBLIC_KEY"&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_PUBLIC_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"MARVEL_PRIVATE_KEY"&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_PRIVATE_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"MARVEL_API_BASE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gateway.marvel.com/v1/public"&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;/div&gt;



&lt;p&gt;You’ll notice that two keys are needed. You can get those from the &lt;a href="https://developer.marvel.com" rel="noopener noreferrer"&gt;https://developer.marvel.com&lt;/a&gt; website after registering.&lt;/p&gt;

&lt;p&gt;What if you don’t want to store keys directly in the file? With VS Code you can use the following confirmation. When you start the MCP server you’ll be prompted to enter the key and it’ll store it for you (but not show it). Here’s what that configuration looks like:  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code MCP Server Configuration&lt;/strong&gt;  &lt;strong&gt;with Inputs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"mcp"&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;"inputs"&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;"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;"promptString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"marvel-public-api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Marvel public API Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"password"&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="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;"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;"promptString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"marvel-private-api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Marvel private API Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"password"&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="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;"servers"&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;"marvel-mcp"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"@codewithdan/marvel-mcp"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/PATH/TO/marvel-mcp/dist/index.js"&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;"env"&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;"MARVEL_PUBLIC_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${input:marvel-public-api-key}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"MARVEL_PRIVATE_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${input:marvel-private-api-key}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"MARVEL_API_BASE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gateway.marvel.com/v1/public"&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;/div&gt;



&lt;p&gt;Once the configuration is in place and the MCP server is started by selecting the Start option above “marvel-mcp” in the file, the &lt;strong&gt;@codewithdan/marvel-mcp&lt;/strong&gt; package will be downloaded from npm and the server will be run locally. From there, a user can interact with the MCP host and ask questions about Marvel characters and comics. Here’s an example of doing that from GitHub Copilot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2025/04/2025-03-30_00-57-50.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2h41lw250tdqg49c347.png" alt="Example of a prompt in GitHub Copilot in VS Code that triggers a Marvel MCP server tool." width="605" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Examples of additional prompts could include:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What comics is Wolverine in?&lt;/p&gt;

&lt;p&gt;Which characters appear in the Avengers comics?&lt;/p&gt;

&lt;p&gt;What characters are in the Hedge Knight II: Sworn Sword (2007) comic?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the MCP server’s tools are known to the MCP host, the AI system should call them anytime it needs an answer that it can’t provide on its own.&lt;/p&gt;

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

&lt;p&gt;And there you have it—your very own MCP server powered by the Marvel universe! While you might not be saving the world with superhero data in your day job, this project gives you a solid blueprint for integrating &lt;em&gt;any&lt;/em&gt; external API into an AI system using the Model Context Protocol.&lt;/p&gt;

&lt;p&gt;Peek under the hood at the code, schemas, tools, and server setup, and you’ll be well on your way to building your own API-powered AI sidekicks. Whether it’s fetching weather data, querying a knowledge base, or interfacing with your company’s internal tools, the process is the same.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/DanWahlin/marvel-mcp" rel="noopener noreferrer"&gt;Marvel MCP Server Project on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.marvel.com/documentation/getting_started" rel="noopener noreferrer"&gt;Marvel Developer API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.org" rel="noopener noreferrer"&gt;Model Context Protocol Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod Validation Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openapis.org" rel="noopener noreferrer"&gt;Open API Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>marvel</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Getting Started with Azure Static Web Apps</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Mon, 14 Nov 2022 21:13:00 +0000</pubDate>
      <link>https://dev.to/danwahlin/getting-started-with-azure-static-web-apps-4a8j</link>
      <guid>https://dev.to/danwahlin/getting-started-with-azure-static-web-apps-4a8j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dwzr7rcnaab3r5x6eh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dwzr7rcnaab3r5x6eh4.png" alt="Azure Static Web Apps Overview Diagram" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What does it take to deploy a modern JavaScript web app? Your initial response might be, “Copy the files up to the server – how hard could it be?”.&lt;/p&gt;

&lt;p&gt;The reality is that deploying modern JavaScript apps is a bit more complicated than simply copying files up to a server. For example, let’s say that you have a Single Page Application (a static web application) built with React, Vue, Angular, or another technology that hits an API, supports user logins and roles, and has to secure specific server-side routes. To deploy the app you’d need to do something like the following at a minimum:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the application and generate the bundles.&lt;/li&gt;
&lt;li&gt;Build the APIs (depending upon what technology is used).&lt;/li&gt;
&lt;li&gt;Setup a server that can host the SPA bundles and run the APIs.&lt;/li&gt;
&lt;li&gt;If the static web app and APIs are on separate servers, configure CORS or a reverse proxy.&lt;/li&gt;
&lt;li&gt;Configure SSL on the server.&lt;/li&gt;
&lt;li&gt;Add a custom domain.&lt;/li&gt;
&lt;li&gt;Configure a default fallback route so that the static web app’s client-side routes work properly and you don’t get a 404.&lt;/li&gt;
&lt;li&gt;Deploy the SPA bundles to the server.&lt;/li&gt;
&lt;li&gt;Deploy the API binaries or scripts up to the server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Are there any additional considerations to take into account? Definitely! Here are a few additional ones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a “staging” environment that mirrors the production environment so that you can do testing and QA before going to production.&lt;/li&gt;
&lt;li&gt;Integrate user authentication and authorization from a cloud provider or 3rd party and secure application routes.&lt;/li&gt;
&lt;li&gt;Automate the build process for the static web app and APIs and create a build pipeline.&lt;/li&gt;
&lt;li&gt;Deploy the static web app to a CDN or to multiple servers around the world. &lt;/li&gt;
&lt;li&gt;Deploy the app’s APIs to a cluster of servers if they need to handle variable loads.&lt;/li&gt;
&lt;li&gt;More…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whew….that is a lot of work! Isn’t it supposed to be easy to deploy a static web app and get it up and running? When you factor in the creation of a “staging” site, authentication/authorization, server configuration, dealing with server-side and client-side routes, global distribution of your app (if required), and other requirements your head can start to spin.&lt;/p&gt;

&lt;p&gt;Are there any services out there that can help simplify the process of deploying a static web app and its associated APIs? You always do it yourself using various cloud provider services but you’d have to setup storage, web hosting, APIs, manage build and deployments, SSL certs, custom domains, handle security, and much more. You could also use services like Netlify (&lt;a href="https://netlify.com" rel="noopener noreferrer"&gt;https://netlify.com&lt;/a&gt;), Firebase (&lt;a href="https://firebase.google.com/" rel="noopener noreferrer"&gt;https://firebase.google.com&lt;/a&gt;), and many others as well.&lt;/p&gt;

&lt;p&gt;Fortunately, there’s a new kid/service on the block. Azure Static Web Apps. I already use Azure for all of my deployments so I’m really excited about this relatively new functionality Microsoft has added to Azure. Let’s look at how it works.&lt;/p&gt;

&lt;p&gt;If you’d prefer to watch a video, here’s one I created that also goes through the steps discussed in this post.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting Started with Azure Static Web Apps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/oPqBuLfIXII"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Azure Static Web Apps
&lt;/h2&gt;

&lt;p&gt;Microsoft announced the Azure Static Web Apps service at the &lt;a href="https://register.build.microsoft.com/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;Build 2020 conference&lt;/a&gt; so it's been out for awhile now. I was fortunate to get early access back then and have been really impressed with the functionality they’re providing. Since then, they’ve made the service “GA” (generally available) and currently support 2 plans. The free plan allows you to get started absolutely free while the standard plan includes all of the free features as well as the ability to customize functionality such as authentication and APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/05/swa-plans.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77ydhvczlousm2c321fz.png" alt="Azure Static Web Apps Plans" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can view pricing details for the standard plan at &lt;a href="https://azure.microsoft.com/en-us/pricing/details/app-service/static?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;https://azure.microsoft.com/en-us/pricing/details/app-service/static&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are the basic steps to get started with Azure Static Web Apps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Push your app code to Github.&lt;/li&gt;
&lt;li&gt;Sign-in to the Azure Portal, search for “Static Web App”, and select the &lt;strong&gt;Create&lt;/strong&gt; button. &lt;/li&gt;
&lt;li&gt;Fill out the form, sign-in to Github, and select your repository and branch.&lt;/li&gt;
&lt;li&gt;Define where your app, APIs, and build output are located.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Create&lt;/strong&gt; button and watch the magic happen!&lt;/li&gt;
&lt;li&gt;View your static web app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before going through these steps you’ll need to have an Azure account. If you don’t have one you can setup a free trial account at &lt;a href="https://aka.ms/azure-free-acct" rel="noopener noreferrer"&gt;https://azure.microsoft.com/free&lt;/a&gt;. Let’s walk through each of these steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1. Push Your App Code to Github
&lt;/h2&gt;

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

&lt;p&gt;If you’re already using Github to store your code then this first step is the easiest of all. If you’re new to Github check out &lt;a href="https://help.github.com/en/github/getting-started-with-github/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;how to started using it.&lt;/a&gt; Believe it or not, once your static web app is on Github and your app is ready to try out, the hard part is done!&lt;/p&gt;

&lt;p&gt;If your app has APIs that you want to host in Azure then you can use &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; (Node.js 12, .NET Core 3.1, or Python 3.8 are supported – &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/apis/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;check the docs&lt;/a&gt; for updates). You can use Azure Static Web Apps without any APIs at all of course. Maybe you have a truly static web app that doesn’t need to call out to a server for data. Or, if your app does have APIs and they’re hosted somewhere else that’s fine too. They’re flexible!&lt;/p&gt;

&lt;p&gt;If your app does have APIs that you want to host in Azure and you’re new to Azure Functions, here’s a quick overview of what they are and what you can do with them. Azure Functions provide a “serverless” environment for hosting a variety of APIs that can serve data over HTTP or integrate with other Azure services. An Azure Function written with JavaScript consists of an  &lt;strong&gt;index.js&lt;/strong&gt;  file that contains your code as well as a  &lt;strong&gt;function.json&lt;/strong&gt;  file that defines the inputs and outputs for the function. Here’s an example of a function that is triggered by an HTTP request and returns JSON: function that is triggered by an HTTP request and returns JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const customers = require('../data/customers.json');

module.exports = async function (context, req) {
    context.res = {
        headers: {
          'Content-Type': 'application/json'    
        },
        body: customers
    };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The input and output bindings (the type of data that flows in and out of the function) can be defined in the function.json file. Here’s an example of input/output bindings for the previous function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get"
      ],
      "route": "customers/"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is triggered by an HTTP GET request to &lt;strong&gt;https://[yourserver.com]/api/customers&lt;/strong&gt;. The incoming request object is named &lt;strong&gt;req&lt;/strong&gt;. The function returns a response using an object named &lt;strong&gt;res&lt;/strong&gt;. Although a complete discussion of Azure Functions is outside the scope of this post, they’re really powerful and definitely worth looking into more.&lt;/p&gt;

&lt;p&gt;Once your static web app and Azure Functions APIs are up on Github, you’re ready to create a static web app service in Azure. Let’s look at that process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2. Sign-in to the Azure Portal, Search for “Static Web Apps”, and click the Create button
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7y7g98wltu9ty5i4o3tv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7y7g98wltu9ty5i4o3tv.png" width="800" height="152"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Searching for the Static Web Apps resource in the Azure portal.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://portal.azure.com/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;https://portal.azure.com&lt;/a&gt;, sign-in, and use the search box at the top to locate the &lt;strong&gt;Static Web Apps&lt;/strong&gt; service. Select it to get to the service’s information page. Take a few moments to read about what the service offers and when you’re ready, click the &lt;strong&gt;Create&lt;/strong&gt; button to get started.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3. Fill Out the Form, Sign-in to Github, and Select Your Repository
&lt;/h2&gt;

&lt;p&gt;In this step you’ll fill out the Static Web Apps form and sign-in to Github to select your repository. Here are the fields to fill out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select your Azure subscription.&lt;/li&gt;
&lt;li&gt;Create or select a &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;Resource Group&lt;/a&gt; (a container that holds related resources such as your new static web app functionality)&lt;/li&gt;
&lt;li&gt;Name your app.&lt;/li&gt;
&lt;li&gt;Select a region.&lt;/li&gt;
&lt;li&gt;Select a SKU/plan.&lt;/li&gt;
&lt;li&gt;Sign-in to Github and select your org, repo, and branch. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you’re done filling out the form click the &lt;strong&gt;Next: Build &amp;gt;&lt;/strong&gt; button.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Define Where Your App, APIs, and Build Output are Located
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/05/create-swa-app-portal.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrqeebm62cbhybyad34u.png" width="731" height="982"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Create a Static Web App&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The next step is to define where your app is located in the repository, where your Azure Functions APIs are located, and the directory where your build artifacts (your bundles) are located. You can even preview the workflow file that will be added to your Github repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/05/swa-build-details.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgc7ytmwxzf1qmltj87e.png" width="725" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After entering that information click the &lt;strong&gt;Review + create&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; Make sure you enter correct values for your root app location (where your package.json file lives), API location (where your Azure function APIs live if you’re publishing them to Azure), and whatever directory your app builds to (note that this is relative to where your app location is) because this will NOT work otherwise. I’ll provide more information about checking out your build status later in this post.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Click the Create Button and Watch the Magic Happen!
&lt;/h2&gt;

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

&lt;p&gt;It’s time to launch your static web app! Review the summary information provided and then click the &lt;strong&gt;Create&lt;/strong&gt; button. Go grab a coffee, kick back, relax, and watch a (super short) YouTube video while a &lt;a href="https://github.com/features/actions/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;Github Action&lt;/a&gt; builds your code and deploys it to Azure automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/05/swa-review-portal-2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1q6bbk9chhp0k5j9f9j.png" width="540" height="498"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: View Your Static Web App
&lt;/h2&gt;

&lt;p&gt;OK – coffee time’s over! Once your static web app is created, click the &lt;strong&gt;Go to resource&lt;/strong&gt; button.&lt;/p&gt;

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

&lt;p&gt;Go to your newly created static web app and click the site’s URL to view it.&lt;/p&gt;

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

&lt;p&gt;If you’d like to see the build in action on Github, click the &lt;strong&gt;blue arrow&lt;/strong&gt;  above the site’s URL (note that this will disappear after a while) or the  &lt;strong&gt;GitHub Action runs&lt;/strong&gt;  link: &lt;/p&gt;

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

&lt;p&gt;Here’s an example of what the Github Action build created in your repository by Azure Static Web App looks like. This repository is located at &lt;a href="https://github.com/DanWahlin/Angular-JumpStart" rel="noopener noreferrer"&gt;https://github.com/DanWahlin/Angular-JumpStart&lt;/a&gt; if you want to try one out that already has an app and functions available. Every time you push code to your chosen repository branch the Github Action build process kicks off automatically.&lt;/p&gt;

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

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

&lt;p&gt;Let’s look at an app that was deployed to Azure Static Web Apps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying a Static Web App and API
&lt;/h2&gt;

&lt;p&gt;To try out the new Azure Static Web Apps functionality I decided to use a sample Angular project I have on Github called &lt;a href="https://github.com/DanWahlin/Angular-JumpStart" rel="noopener noreferrer"&gt;Angular Jumpstart&lt;/a&gt;. It originally relied on Node.js/Express for the back-end APIs and is easy to get going locally. See the &lt;strong&gt;README.md&lt;/strong&gt; file for more details on getting it running on your machine.&lt;/p&gt;

&lt;p&gt;The challenge with the original version of the app was that I needed to understand what Azure Static Web Apps wanted and I needed to convert my existing Node.js RESTful APIs into &lt;a href="https://azure.microsoft.com/en-us/services/functions/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; (I no longer need a server to host the APIs on which is great!). You can find &lt;a href="https://docs.microsoft.com/en-us/learn/modules/shift-nodejs-express-apis-serverless/?WT.mc_id=m365-28924-dwahlin" rel="noopener noreferrer"&gt;documentation on the conversion process&lt;/a&gt; on the Microsoft docs site if you’re interested.&lt;/p&gt;

&lt;p&gt;After getting everything in place I decided there was no time like the present to get things started. I went through the steps listed above to create the Azure Static Web App and my first build started on Github. Looking good….looking good….damn…it failed. I hadn’t read any of the docs at this point but I figured it’d be interesting to see how things went without any modifications (aside from the Node –&amp;gt; Azure Functions conversion).&lt;/p&gt;

&lt;p&gt;It turned out that the first build failed due to putting an incorrect path in for the build output (step 4 above). I fixed that, got the build working, and successfully deployed the site. Success….or so I thought. While the shell of the site loaded, none of the API calls worked which meant no data loaded into the web page.&lt;/p&gt;

&lt;p&gt;I jumped on a call with my buddy &lt;a href="https://twitter.com/john_papa" rel="noopener noreferrer"&gt;John Papa&lt;/a&gt; from Microsoft (fortunately for me he had been spending a lot of time with this new service) and he had me adjust a few things in my functions. The main change was “api” needed to be taken out of the route values in the &lt;strong&gt;function.json&lt;/strong&gt; files. For example, &lt;strong&gt;api/customers&lt;/strong&gt; was converted to &lt;strong&gt;customers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once that change was made along with a few other minor ones, the site sprung to life. However, if I refreshed the page I got a 404 error because the route was evaluated on the server-side instead of redirecting back to &lt;strong&gt;index.html&lt;/strong&gt;. Azure Static Web Apps to the rescue! They have a nice routing solution (which I’m only going to scratch the surface on in this post) that lets you handle the proper redirect to the static web app client-side routes.&lt;/p&gt;

&lt;p&gt;You can add a &lt;strong&gt;staticwebapp.config.json&lt;/strong&gt; file into the root of your project to handle redirecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{   
  "navigationFallback": {     
    "rewrite": "/index.html" 
  } 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I added the &lt;strong&gt;staticwebapp.config.json&lt;/strong&gt; file the site was rebuilt/redeployed and everything worked as expected. You can get more information about routing, handling client-side redirects, and even securing server-side APIs using authentication/authorization &lt;a href="https://aka.ms/swadocs" rel="noopener noreferrer"&gt;in the Azure Static Web Apps docs&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Now any time I want to make a change I simply push it up to Github, that kicks off the Github Action build process and deployment, and the change is in production on Azure within a few minutes. Pretty amazing! Feel free to clone the &lt;a href="https://github.com/DanWahlin/Angular-JumpStart" rel="noopener noreferrer"&gt;Angular Jumpstart&lt;/a&gt; project and try out the steps shown earlier on your own.&lt;/p&gt;

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

&lt;p&gt;Although deploying modern JavaScript apps can be pretty challenging when you factor in all of the required tasks, Azure Static Web Apps greatly simplifies the process and makes it a piece of cake to deploy an app once you have it setup and configured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; If you’re building &lt;a href="https://aka.ms/ms-teams-docs" rel="noopener noreferrer"&gt;Microsoft Teams apps&lt;/a&gt; then Azure Static Web Apps can provide a great way to host your Teams app depending on the technology used by the app!&lt;/p&gt;

&lt;p&gt;So what’s next? There’s quite a bit more you can do with Azure Static Web Apps such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a custom domain to your static web app using the Azure portal.&lt;/li&gt;
&lt;li&gt;Create a staging slot in Azure Static Web Apps to test your app (for example test a pull request) before swapping it over to production.&lt;/li&gt;
&lt;li&gt;Add authentication/authorization using Microsoft, Google, Facebook, Twitter, or Github. You can add users, associate roles with back-end routes and more.&lt;/li&gt;
&lt;li&gt;Although this example shows an Angular application, you can deploy many other app types as well:

&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Vue&lt;/li&gt;
&lt;li&gt;Hugo&lt;/li&gt;
&lt;li&gt;Svelte&lt;/li&gt;
&lt;li&gt;Gatsby&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;More…&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Take Azure Static Web Apps for a spin and see what you think! Here are some additional links you can visit to learn more. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static Web Apps docs:
&lt;a href="https://aka.ms/azure-swa-docs" rel="noopener noreferrer"&gt;https://aka.ms/azure-swa-docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Static Web Apps Learn modules (Angular, React, Svelte, or Vue JavaScript app and API):
&lt;a href="https://aka.ms/swaframeworks" rel="noopener noreferrer"&gt;https://aka.ms/azure-swa-learn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Static web app with the Gatsby static site generator:
&lt;a href="https://aka.ms/azure-swa-gatsby" rel="noopener noreferrer"&gt;https://aka.ms/azure-swa-gatsby&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>angular</category>
      <category>staticwebapps</category>
    </item>
    <item>
      <title>Video: Show a user’s emails in an ASP.NET Core app using Microsoft Graph</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Mon, 14 Nov 2022 21:08:46 +0000</pubDate>
      <link>https://dev.to/danwahlin/video-show-a-users-emails-in-an-aspnet-core-app-using-microsoft-graph-2671</link>
      <guid>https://dev.to/danwahlin/video-show-a-users-emails-in-an-aspnet-core-app-using-microsoft-graph-2671</guid>
      <description>&lt;p&gt;I’ve been working a lot with &lt;a href="https://dot.net" rel="noopener noreferrer"&gt;.NET Core&lt;/a&gt; and &lt;a href="https://aka.ms/ms-graph-docs" rel="noopener noreferrer"&gt;Microsoft Graph&lt;/a&gt; lately and decided to put together a short video based on a &lt;a href="https://aka.ms/learn-msgraph-email" rel="noopener noreferrer"&gt;Microsoft Learn module&lt;/a&gt; covering how the technologies can be used together. If you haven’t used Microsoft Graph before, it provides a secure, unified API to access organizational data and intelligence (data stored in Microsoft 365 for example).   &lt;/p&gt;

&lt;p&gt;So why would you ever want to access a signed in user’s emails and include them in your custom app? The simple answer is, “Bring organizational data where your users need it everyday!”. Instead of users switching from your app to find a relevant email, calendar event, Microsoft Teams chat (and more) by jumping between various productivity apps, you can pull that type of data directly into your custom app. This allows users to work more efficiently and make more informed decisions all while minimizing context shifts.&lt;/p&gt;

&lt;p&gt;In this video I’ll introduce you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The role of security in making Microsoft Graph calls.&lt;/li&gt;
&lt;li&gt;Microsoft Identity and Microsoft Graph Middleware configuration.&lt;/li&gt;
&lt;li&gt;The role of permissions/scopes and access tokens.&lt;/li&gt;
&lt;li&gt;The Microsoft Graph .NET Core SDK and how it can be used.&lt;/li&gt;
&lt;li&gt;How to create reusable classes that handle making Microsoft Graph calls.&lt;/li&gt;
&lt;li&gt;Dependency injection and how it can be used to access a GraphServiceClient object.&lt;/li&gt;
&lt;li&gt;How to retrieve a batch of email messages using the UserMessagesCollectionRequest class.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show a user’s emails in an ASP.NET Core app using Microsoft Graph
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/acnFrkBL1kE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>microsoftgraph</category>
      <category>csharp</category>
      <category>microsoftcloud</category>
    </item>
    <item>
      <title>New Video Series: All Things Microsoft Cloud</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Mon, 14 Nov 2022 21:06:33 +0000</pubDate>
      <link>https://dev.to/danwahlin/new-video-series-all-things-microsoft-cloud-3114</link>
      <guid>https://dev.to/danwahlin/new-video-series-all-things-microsoft-cloud-3114</guid>
      <description>&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/08/2022-08-24_11-23-45.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffibixdsx6m3qikpiucyl.png" alt="All Things Microsoft Cloud - Ayca Bas and Dan Wahlin" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had a chance to sit down with my colleague&lt;a href="https://twitter.com/aycabs" rel="noopener noreferrer"&gt;Ayça Baş&lt;/a&gt; as well as several special guests to talk about how different technologies across the Microsoft Cloud can be integrated together to build a variety of applications. Check out the different interviews in the video series below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Microsoft Cloud?
&lt;/h2&gt;

&lt;p&gt;Ayça and I talk about the overall Microsoft Cloud and services that are available. While &lt;a href="https://docs.microsoft.com/azure/developer/" rel="noopener noreferrer"&gt;Azure&lt;/a&gt; is a central part of the Microsoft Cloud, you can also integrate with services across &lt;a href="https://docs.microsoft.com/microsoft-365/?view=o365-worldwide" rel="noopener noreferrer"&gt;Microsoft 365&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/power-platform/" rel="noopener noreferrer"&gt;Power Platform&lt;/a&gt;, and &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Ayça and I also discuss a new &lt;a href="https://docs.microsoft.com/azure/architecture/guide/microsoft-cloud/overview" rel="noopener noreferrer"&gt;Build applications on the Microsoft Cloud&lt;/a&gt; document that walks IT leaders, architects, and developers through the options available to leverage everything the Microsoft Cloud has to offer.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/SfAy0f2ir5k"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft Cloud and Microsoft Graph
&lt;/h2&gt;

&lt;p&gt;In this video we talk with &lt;a href="https://twitter.com/yina_arenas" rel="noopener noreferrer"&gt;Yina Arenas&lt;/a&gt; about the role of Microsoft 365 and Microsoft Graph in the overall Microsoft Cloud. Yina shares the story of how Microsoft Graph was created and discusses the powerful APIs it offers to enable developers to integrate Microsoft 365 (and other) data into their applications.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/MXq-M6qRffE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft Cloud and Power Platform
&lt;/h2&gt;

&lt;p&gt;In this video we talk about the role of Power Platform in the overall Microsoft Cloud. &lt;a href="https://twitter.com/aprildunnam" rel="noopener noreferrer"&gt;April Dunnam&lt;/a&gt; discusses how to get started with Power Platform, what fusion development is, how to integrate with different APIs using connectors and Azure API Management, the VS Code extension for Power Platform, and more.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/z19QZwmy1yg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft Cloud and Microsoft 365/Microsoft Teams
&lt;/h2&gt;

&lt;p&gt;In this video we talk about the role of Microsoft 365 and Microsoft Teams in the overall Microsoft Cloud. &lt;a href="https://twitter.com/Bob1German" rel="noopener noreferrer"&gt;Bob German&lt;/a&gt; discusses how to get started building apps for Microsoft Teams using Power Platform and how custom apps and services can be built using the Teams Toolkit. He also shares information about a Microsoft Teams App Camp workshop that developers can take to dive in deeper.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/777ypUr2hwA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft Cloud and Accessibility
&lt;/h2&gt;

&lt;p&gt;Accessibility plays a prominent role across Microsoft Cloud services. In this video we talk with &lt;a href="https://twitter.com/donasarkar" rel="noopener noreferrer"&gt;Dona Sarkar&lt;/a&gt; about different accessibility features built-into cloud services and tools that developers can utilize to increase accessibility in their custom applications.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/h0gByFYotZw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft Cloud and GitHub
&lt;/h2&gt;

&lt;p&gt;In this video we talk with &lt;a href="https://twitter.com/ToddAnglin" rel="noopener noreferrer"&gt;Todd Anglin&lt;/a&gt; about how GitHub fits into the overall Microsoft Cloud and some of the technologies you can use to simplify integration with Azure.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/a7zwFpkPoy0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>microsoftcloud</category>
      <category>azure</category>
      <category>microsoft365</category>
      <category>powerplatform</category>
    </item>
    <item>
      <title>Getting Started Calling the Microsoft Graph API</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Fri, 04 Nov 2022 04:48:01 +0000</pubDate>
      <link>https://dev.to/danwahlin/getting-started-calling-the-microsoft-graph-api-4265</link>
      <guid>https://dev.to/danwahlin/getting-started-calling-the-microsoft-graph-api-4265</guid>
      <description>&lt;p&gt;In this post I’m going to share a quick tip on how to get started calling the &lt;a href="https://aka.ms/msgraph-overview-docs" rel="noopener noreferrer"&gt;Microsoft Graph API&lt;/a&gt;. If you’re new to Microsoft Graph, here’s a short definition for you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Microsoft Graph provides a secure and unified API that can be used to access Microsoft 365 and other cloud data and intelligence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;NOTE: You can watch a video about everything covered here on the &lt;a href="https://aka.ms/m365youtube" rel="noopener noreferrer"&gt;Microsoft 365 Developer YouTube channel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/f_3wc4UgqTI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In a nutshell, you can use Microsoft Graph to retrieve information about users, groups, emails, Teams chats, OneDrive files, meetings, to-do list tasks, and much more and then pull that data into apps where your users work every day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/04/2021-04-29_10-37-20.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkik0k2xkgr74s6eamhux.png" width="800" height="740"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Retrieving Microsoft 365 Data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That means if I’m the user using your application, I can login and I see my list of meetings or files related to a particular topic listed right in the app. Or, if I have emails that are related to a particular scenario, I can see my emails after logging in. Of course, this is all done in a secure manner where the user has to consent and give their approval.&lt;/p&gt;

&lt;p&gt;Before moving on, it’s important to clarify one thing: Microsoft Graph isn’t graph DB or GraphQL. Microsoft Graph is a RESTful API so the same principles you’ve likely used with other APIs (Web API with .NET, APIs exposed using Express/Node.js, and so forth) can be used.&lt;/p&gt;

&lt;p&gt;To get started with Microsoft Graph you can use the &lt;a href="http://aka.ms/g-explorer" rel="noopener noreferrer"&gt;Microsoft Graph Explorer&lt;/a&gt;. At the Microsoft Graph Explorer website you can see what Microsoft graph APIs are available and practice using them all within the confines of the browser. For example, in the images below you’ll notice there is an area where you can select a sample query to run and an area where the API’s URL is defined. Select the “my profile” option in the &lt;strong&gt;Sample queries&lt;/strong&gt; section to get started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/04/2021-04-29_13-31-15.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0galjgxev43zhb9obd7.png" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Microsoft Graph explorer sample queries&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/04/2021-04-29_13-32-44.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3cxcaz2d31nadycw0ea.png" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Microsoft Graph explore API URLs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As mentioned, Microsoft Graph is a RESTful API used to get Microsoft 365 data. For example, I can call into the profile API and get the logged in user’s profile using the following URL (shown above in the previous image):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://graph.microsoft.com/v1.0/me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you first go to the Microsoft Graph Explorer you won’t be logged in. That means you’ll be getting anonymous data back for a user’s profile and you’ll see a display name of Megan Bowen is used. You can also get some information about Megan’s email address and some other info as well in her profile. Notice that the data returned is JSON (JavaScript Object Notation) data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/04/2021-04-29_13-35-40.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4ir0wlb5txmzlrojkt7.png" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;JSON data returned for a user’s profile.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In addition to that, you can run other queries. If you want to do this for Megan (the anonymous user you’ll see) you can leave it as is, but if you want to do it for yourself, you can either log in with your credentials or better yet you could log in with a &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/dev-program?WT.mc_id=m365-20792-dwahlin" rel="noopener noreferrer"&gt;free developer tenant&lt;/a&gt; so that you can safely play around.&lt;/p&gt;

&lt;p&gt;If you go to the sample queries section and click on “my photo” you’ll see Megan’s photo displayed in the browser. Look at the API URL and you’ll notice the following value displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://graph.microsoft.com/v1.0/me/photo/$value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you delete &lt;strong&gt;$value&lt;/strong&gt; , you’ll get back JSON data about the image. Notice that it contains height, width, and some other data as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/04/2021-04-29_13-46-06.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fheekc6gvtynsw8rwev3s.png" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Data returned about a user’s profile image.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can get to Megan’s email messages as well. I want to emphasize again that you have to have a logged in user who has consented to access this information. If you want to display a user’s email messages, they’d have to log in first. If they approve it, you could pull their mail in and display it to them in an app.&lt;/p&gt;

&lt;p&gt;For example, maybe you’re working on a sales application right now, and you want to pull in sales specific emails so that they can see what they’ve said in the past to customers. You could filter based on subject, for example, and then display the body of the message to the sales person after they’ve logged in.&lt;/p&gt;

&lt;p&gt;You can also retrieve calendar events and more. If you go the sample queries search box (lower left of the screen) and type “calendar” you’ll notice you can retrieve calendar events for the next week or all events. Maybe you want all upcoming events but only want to retrieve a limited amount of data about each event. If you click on the “all events in my calendar” query you can get that data. Looking at the JSON that’s returned you’ll see quite a bit of data displayed.&lt;/p&gt;

&lt;p&gt;If you only want the &lt;strong&gt;subject&lt;/strong&gt; , &lt;strong&gt;body&lt;/strong&gt; , and maybe the &lt;strong&gt;start&lt;/strong&gt; and the &lt;strong&gt;end&lt;/strong&gt; dates, you can adjust that in the URL by deleting the properties you don’t want. The properties to return in the JSON data are controlled using the &lt;strong&gt;$select&lt;/strong&gt; query parameter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/04/2021-04-29_13-52-27.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8igqwvrjcif8i1aoyzp.png" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Changing returned properties using the $select query parameter.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now run the query and you’ll notice that only those properties are returned. You can also add different types of filtering if you’d like. You can add an &lt;strong&gt;&amp;amp;&lt;/strong&gt; to the end of the URL and type &lt;strong&gt;$filter&lt;/strong&gt; equals “your query here”. Or, you can grab something greater than a date, less than a date, etc. Here’s an example of using &lt;strong&gt;ge&lt;/strong&gt; to grab calendar events greater than or equal to a specific date. Notice the use of the &lt;strong&gt;$filter&lt;/strong&gt; query parameter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2021/04/2021-04-29_13-57-09.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jdteuje4loibkk8lefq.png" width="800" height="66"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Using the $filter query paramter&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After selecting &lt;strong&gt;Run query&lt;/strong&gt; you’ll have the same data, but now it’s filtered and only the specific properties you selected are returned! Additional information about selecting, querying, sorting, and performing other types of operations can be found at &lt;a href="https://aka.ms/msgraph-query-params" rel="noopener noreferrer"&gt;https://aka.ms/msgraph-query-params&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;I hope this helps you out as you get started using Microsoft Graph. If you’d like a more hands-on approach to using Graph Explorer and Microsoft Graph in general, check out the free &lt;br&gt;
Microsoft Graph Fundamentals learning path available at &lt;a href="https://aka.ms/learn-graph" rel="noopener noreferrer"&gt;https://aka.ms/learn-graph&lt;/a&gt; or watch the video course at &lt;a href="https://aka.ms/ms-graph-video-course" rel="noopener noreferrer"&gt;https://aka.ms/ms-graph-video-course&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>microsoft365</category>
      <category>microsoftgraph</category>
      <category>microsoftgraphexplorer</category>
      <category>microsoftcloud</category>
    </item>
    <item>
      <title>Start Learning TypeScript with These Short Videos</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Fri, 04 Nov 2022 04:41:59 +0000</pubDate>
      <link>https://dev.to/danwahlin/start-learning-typescript-with-these-short-videos-2c9n</link>
      <guid>https://dev.to/danwahlin/start-learning-typescript-with-these-short-videos-2c9n</guid>
      <description>&lt;p&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; continues to grow in popularity and for good reason. It adds “guard rails” to your code to help you spot issues early on, easily locate problem code, enhance productivity, provide consistency across code, and much more. While there are a lot of &lt;a href="https://www.typescriptlang.org/docs/" rel="noopener noreferrer"&gt;TypeScript resources&lt;/a&gt; out there to get started learning the language, where can you go to get started quickly without wasting a lot of time?   &lt;/p&gt;

&lt;p&gt;I recently published a series of short videos on TypeScript core concepts that can provide a great starting point. The videos are short, super focused, and many of them use the online &lt;a href="https://www.typescriptlang.org/play" rel="noopener noreferrer"&gt;TypeScript Playground&lt;/a&gt; to demonstrate different concepts. There are a few videos on getting started working with TypeScript locally on your machine as well.&lt;/p&gt;

&lt;p&gt;Here’s more information about each video.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why Learn TypeScript?
&lt;/h2&gt;

&lt;p&gt;Is it worth your time to learn TypeScript? Short answer (in my opinion anyway) is YES! In this video I’ll walk through 5 reasons learning TypeScript is worth the effort. Since these videos are intended to be short I could only cover 5, but there are many additional reasons as well!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/5S96t9kLC5w"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Adding TypeScript to a VS Code Project
&lt;/h2&gt;

&lt;p&gt;How do you get started using TypeScript, writing, and building your code? I’ll walk you through the basics of that process in this video.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/vtpM7ght-7s"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  3. How to Add WebPack to a TypeScript Project
&lt;/h2&gt;

&lt;p&gt;WebPack’s scary right? Well, truth be told it can be intimidating at times, but it’s pretty easy to use it in TypeScript projects. I’ll walk you through the process in this video.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ILEX6mKgB2E"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Getting Started with TypeScript Types
&lt;/h2&gt;

&lt;p&gt;It’s no secret that TypeScript adds “strong typing” into your code (they call it TypeScript for a reason). In this video I’ll explain the primitive data types available and show how you can get started using them.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/BdRFqLru3Z8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Using Classes in TypeScript
&lt;/h2&gt;

&lt;p&gt;Classes are a feature available in JavaScript that can be used to encapsulate your code. They’re not needed for every type of project, but it’s good to know what they’re capable of. In this video I’ll introduce classes and show how they can be used in TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/A4V1sU3p0S4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Using Interfaces in TypeScript
&lt;/h2&gt;

&lt;p&gt;In an earlier video I introduced the concept of TypeScript types. In this video, I walk you through how you can use interfaces to build custom types and explain why you may want to do that. Interfaces are “code contracts” that can be used to describe the “shape” of an object, drive consistency across objects, and more.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/dzfCgPFJyr4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Using Generics with TypeScript
&lt;/h2&gt;

&lt;p&gt;Generics are “code templates” that can be reused in your code base. In this video I introduce the concept of generics and show simple examples of how they can be used in TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/nmCKKIxebJc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Are there more topics that I could have covered? Yep – there’s always more. However, these videos should provide you with a solid starting point to understand core concepts and features. There are a lot of additional resources out there to learn TypeScript (you can start with the &lt;a href="https://www.typescriptlang.org/docs/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; or the &lt;a href="https://www.typescriptlang.org/docs/handbook/intro.html" rel="noopener noreferrer"&gt;handbook&lt;/a&gt;), but I hope these short videos help get you started quickly. I’m personally a huge fan of TypeScript and highly recommend making time to learn it.&lt;/p&gt;

&lt;p&gt;If you’d like to dive into more details about TypeScript fundamentals, check out the &lt;a href="https://pluralsight.pxf.io/a1LxaZ" rel="noopener noreferrer"&gt;TypeScript Fundamentals video course on Pluralsight&lt;/a&gt; that &lt;a href="https://twitter.com/john_papa" rel="noopener noreferrer"&gt;John Papa&lt;/a&gt; and I created.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>development</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Migrating a Local Node Script to Azure Functions using VS Code</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Fri, 04 Nov 2022 03:48:40 +0000</pubDate>
      <link>https://dev.to/danwahlin/migrating-a-local-node-script-to-azure-functions-using-vs-code-225j</link>
      <guid>https://dev.to/danwahlin/migrating-a-local-node-script-to-azure-functions-using-vs-code-225j</guid>
      <description>&lt;p&gt;I have a work project that uses GitHub APIs to access stats about specific repos (views, clones, forks, etc.). It was pretty straightforward to get the project running locally using GitHub’s &lt;a href="https://www.npmjs.com/package/@octokit/rest" rel="noopener noreferrer"&gt;Octokit REST package&lt;/a&gt; and with a little work I had a working Node script that could be run to retrieve the data and display it in the console. That was a good start, but the script functionality needed to be consumed by others in my organization as well as by services such as Power Automate. What to do?&lt;/p&gt;

&lt;p&gt;While I could easily convert the script into a &lt;a href="https://www.npmjs.com/package/express" rel="noopener noreferrer"&gt;Node/Express&lt;/a&gt; API and publish it to &lt;a href="https://docs.microsoft.com/azure/app-service/overview" rel="noopener noreferrer"&gt;Azure App Service&lt;/a&gt;, I decided to go with &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-vs-code?tabs=nodejs" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; since when you boil the script down to the basics, its job is to handle a request and return data. It doesn’t need to be constantly accessed so a consumption based model works well.&lt;/p&gt;

&lt;p&gt;Here’s the process I went through to convert my local script to an &lt;a href="https://docs.microsoft.com/azure/azure-functions/" rel="noopener noreferrer"&gt;Azure Function&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install the Azure Functions Extension for VS Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/08/image.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2laeq7b8y7kcelhrcpyr.png" width="800" height="473"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Creating a Function using VS Code and Extensions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wanted to develop the Azure Function locally and knew that the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions" rel="noopener noreferrer"&gt;Azure Functions extension for VS Code&lt;/a&gt; could help with that. It allows you to do everything on your local machine and then publish to Azure Functions once you’re ready.&lt;/p&gt;

&lt;p&gt;To get started you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://github.com/DanWahlin/GitHub-API-Demo" rel="noopener noreferrer"&gt;my project&lt;/a&gt; in VS Code.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions" rel="noopener noreferrer"&gt;extension&lt;/a&gt; (I already had it installed, but you’ll want it).&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Azure&lt;/strong&gt; in the VS Code sidebar.&lt;/li&gt;
&lt;li&gt;Locate the &lt;strong&gt;Workspace&lt;/strong&gt; section and click the &lt;strong&gt;+&lt;/strong&gt; icon.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Create Function&lt;/strong&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since I only had a simple Node project at this point, I received the following prompt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/08/image-1.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspgehjxj5d518vs8dql1.png" width="260" height="215"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Prompt to create an Azure Functions project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From there I selected the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt; : TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt; : HTTP trigger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function Name&lt;/strong&gt; : getGitHubRepoStats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization level&lt;/strong&gt; : Anonymous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was prompted to overwrite my existing &lt;strong&gt;.gitignore&lt;/strong&gt; and &lt;strong&gt;package.json&lt;/strong&gt; files. I said “yes” since I only had @octokit/rest in the Node dependencies list. It finished creating the project and displayed the shiny new function in the editor. It added the following into my project (in addition to a few other items):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/08/image-2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficp2mpynb92zwv4t7qvk.png" width="313" height="151"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Files added by the Azure Functions extension.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Good progress! Time to get my existing code converted to an Azure Function.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Merge the Local Script Code into the Azure Function
&lt;/h2&gt;

&lt;p&gt;My initial script looked like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Octokit&lt;/span&gt; &lt;span class="p"&gt;}&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;@octokit/rest&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt; &lt;span class="p"&gt;}&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="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create personal access token (with repo --&amp;gt; public rights) at https://github.com/settings/tokens&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;octokit&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;ownersRepos&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;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;getStats&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;log&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="nx"&gt;log&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Doing this to simulate what's it like in Azure Functions&lt;/span&gt;
    &lt;span class="nx"&gt;ownersRepos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getRepos&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;ownersRepos&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;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for &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;repo&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;ownersRepos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;octokit&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;Octokit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&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;ownerRepo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&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;clones&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;getClones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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;forks&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;getTotalForks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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;views&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;getPageViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getTodayRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;views&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="nx"&gt;stats&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;stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRepos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Need to set env variable GITHUB_REPOS&lt;/span&gt;
        &lt;span class="c1"&gt;// export GITHUB_REPOS="[{ \"owner\": \"microsoft\", \"repo\": \"MicrosoftCloud\", \"token\": \"token_value\" }]"&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repos&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;parse&lt;/span&gt;&lt;span class="p"&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;GITHUB_REPOS&lt;/span&gt;&lt;span class="dl"&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;Repos:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repos&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;repos&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;e&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="nx"&gt;e&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTodayRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&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;today&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;Date&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;yesterday&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&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="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T00:00:00Z&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;todayClonesViewsForks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;clones&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="na"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;views&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todayClones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;yesterday&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;todayViews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;yesterday&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;todayClones&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;todayClonesViewsForks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todayClones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&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;todayViews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;todayClonesViewsForks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todayViews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&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="nx"&gt;todayClonesViewsForks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;getClones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// https://docs.github.com/en/rest/metrics/traffic#get-repository-clones&lt;/span&gt;
        &lt;span class="kd"&gt;const&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getClones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; clones:`&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;count&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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="s2"&gt;`Unable to get clones for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. You probably don't have push access.`&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;getTotalForks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// https://docs.github.com/en/rest/repos/forks&lt;/span&gt;
        &lt;span class="kd"&gt;const&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&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;ownerRepo&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;forksCount&lt;/span&gt; &lt;span class="o"&gt;=&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="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;forks_count&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="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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; forks:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forksCount&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;forksCount&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;e&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="nx"&gt;e&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="s2"&gt;`Unable to get forks for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. You probably don't have push access.`&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;getPageViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// https://docs.github.com/en/rest/metrics/traffic#get-page-views&lt;/span&gt;
        &lt;span class="kd"&gt;const&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; visits:`&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;count&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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="s2"&gt;`Unable to get page views for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. You probably don't have push access.`&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;e&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="mi"&gt;0&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 next step was to merge my script into the new Azure Function. Since the Azure Functions extension (with my permission) overwrote my &lt;strong&gt;package.json&lt;/strong&gt; file, I ran &lt;strong&gt;npm install @octokit/rest&lt;/strong&gt; to get the package back into the dependencies list.&lt;/p&gt;

&lt;p&gt;At this point I had the following function code displayed in VS Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;AzureFunction&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;HttpRequest&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="s2"&gt;@azure/functions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpTrigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AzureFunction&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;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;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;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;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;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&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;body&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;responseMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;. This HTTP triggered function executed successfully.&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;This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.&lt;/span&gt;&lt;span class="dl"&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="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="c1"&gt;// status: 200, /* Defaults to 200 */&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;responseMessage&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;httpTrigger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that I had the shell created for the function, I created a new &lt;strong&gt;getStats.ts&lt;/strong&gt; script in the &lt;strong&gt;getGitHubRepoStats&lt;/strong&gt; function folder, copied in my initial code, and changed require statements to import statements at the top of the file. It looked like the following after finishing a few “tweaks”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Octokit&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;@octokit/rest&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;v4&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&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;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create personal access token (with repo --&amp;gt; public rights) at https://github.com/settings/tokens&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Octokit&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;ownersRepos&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;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;getStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;log&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="nx"&gt;log&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;ownersRepos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getRepos&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;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for &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;repo&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;ownersRepos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;octokit&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;Octokit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&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;ownerRepo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&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;clones&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;getClones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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;forks&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;getTotalForks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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;views&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;getPageViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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;yesterdayRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTodayRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yesterdayRow&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="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRepos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&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;repos&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;parse&lt;/span&gt;&lt;span class="p"&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;GITHUB_REPOS&lt;/span&gt;&lt;span class="dl"&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;Repos:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repos&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;repos&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;e&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="nx"&gt;e&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTodayRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&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;today&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;Date&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;yesterday&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&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="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T00:00:00Z&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;todayClonesViewsForks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;clones&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="na"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;views&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todayClones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;yesterday&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;todayViews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;yesterday&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;todayClones&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;todayClonesViewsForks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todayClones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&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;todayViews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;todayClonesViewsForks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todayViews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&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="nx"&gt;todayClonesViewsForks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;getClones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// https://docs.github.com/en/rest/metrics/traffic#get-repository-clones&lt;/span&gt;
        &lt;span class="kd"&gt;const&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getClones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; clones:`&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;count&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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="s2"&gt;`Unable to get clones for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. You probably don't have push access.`&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;getTotalForks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// https://docs.github.com/en/rest/repos/forks&lt;/span&gt;
        &lt;span class="kd"&gt;const&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&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;ownerRepo&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;forksCount&lt;/span&gt; &lt;span class="o"&gt;=&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="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;forks_count&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="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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; forks:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forksCount&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;forksCount&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;e&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="nx"&gt;e&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="s2"&gt;`Unable to get forks for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. You probably don't have push access.`&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;getPageViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// https://docs.github.com/en/rest/metrics/traffic#get-page-views&lt;/span&gt;
        &lt;span class="kd"&gt;const&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; visits:`&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;count&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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="s2"&gt;`Unable to get page views for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&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;ownerRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. You probably don't have push access.`&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;e&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="mi"&gt;0&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;Next, I went into the &lt;strong&gt;getGitHubRepoStats/index.ts&lt;/strong&gt; file, imported the &lt;strong&gt;getStats.ts&lt;/strong&gt; script, and modified the body. Using this approach keeps the function nice and clean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;AzureFunction&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;HttpRequest&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;@azure/functions&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;getStats&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;./getStats&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;httpTrigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AzureFunction&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;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;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;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;HTTP trigger function processed a GitHub repo stats 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;const&lt;/span&gt; &lt;span class="nx"&gt;stats&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;getStats&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;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;The stats&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stats&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;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="nx"&gt;stats&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;httpTrigger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I pressed F5 which then prompted me to install the “core” tools. After the installation completed, it showed several commands in the console, displayed the core tools version, built the code, and launched my new function locally. I hit the &lt;strong&gt;&lt;a href="http://localhost:7071/api/getGitHubRepoStats" rel="noopener noreferrer"&gt;http://localhost:7071/api/getGitHubRepoStats&lt;/a&gt;&lt;/strong&gt; URL shown in the console and….drumroll please….it actually worked! Getting projects to work the first time is rare for me so it was nice to have a quick “win” for once.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create a Function App in Azure
&lt;/h2&gt;

&lt;p&gt;Now that the function was working locally it was time to deploy it to Azure. I stopped my debugging session, went to the command pallet (shift+cmd+p on Mac), and selected &lt;strong&gt;Azure Functions: Create Function App in Azure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/09/image.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhv58kkx0u1miebh20hv.png" width="800" height="215"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Using the Azure Functions: Create Function App in Azure Option in VS Code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you select that option, you’ll be prompted for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Azure subscription to use&lt;/li&gt;
&lt;li&gt;The function name&lt;/li&gt;
&lt;li&gt;The runtime stack (I selected Node.js 16 LTS)&lt;/li&gt;
&lt;li&gt;The region&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. Deploy the Azure Function Code
&lt;/h2&gt;

&lt;p&gt;Once the Azure Function App is created you’ll see a message about viewing the details. The next step is to deploy the code. That can be done by going back to the command pallet in VS Code and selecting &lt;strong&gt;Azure Functions: Deploy to Function App&lt;/strong&gt;. You’ll be asked to select your subscription and Function App name.&lt;/p&gt;

&lt;p&gt;Once the function is created in Azure you can go to the Azure extension in VS Code, expand your subscription, expand your Function App, right-click on the function and select &lt;strong&gt;Browse Website&lt;/strong&gt;. Add “/api/” to the URL and if all of the planets align, you should see data returned from your function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/09/image-2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89lz05thhowfithkb7cq.png" width="489" height="438"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Using the Azure VS Code extension to browser your Azure Functions website&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Environment Variables and Key Vault
&lt;/h2&gt;

&lt;p&gt;You might have noticed that the function code relies on an environment variable named &lt;strong&gt;GITHUB_REPOS&lt;/strong&gt;. I added that key and value into the &lt;strong&gt;Values&lt;/strong&gt; property of the &lt;strong&gt;local.settings.json&lt;/strong&gt; file which is used when running the function locally (that file isn’t checked into source control).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&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;"IsEncrypted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Values"&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;"AzureWebJobsStorage"&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;"FUNCTIONS_WORKER_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;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"GITHUB_REPOS"&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="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;microsoft&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;repo&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MicrosoftCloud&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;token-value&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; }, { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;microsoft&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;repo&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brainstorm-fluidframework-m365-azure&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;token-value&lt;/span&gt;&lt;span class="se"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could deploy the function and have the &lt;strong&gt;GITHUB_REPOS&lt;/strong&gt; value show up automatically in the &lt;strong&gt;Configuration –&amp;gt; Application Settings&lt;/strong&gt; section of the Function App (you’ll see that section in the Azure Portal). In my case that wasn’t good enough though. The &lt;strong&gt;GITHUB_REPOS&lt;/strong&gt; value has GitHub personal access tokens in it that are used to make the API calls. I needed a more secure solution when I ran the function in Azure.&lt;/p&gt;

&lt;p&gt;To handle that, I created a new Azure Key Vault secret that included the data required for the &lt;strong&gt;GITHUB_REPOS&lt;/strong&gt; environment variable. I then went into &lt;strong&gt;Configuration –&amp;gt; Application Settings&lt;/strong&gt; in the Function App and ensured that it had the following key/value pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GITHUB_REPOS=@Microsoft.KeyVault(SecretUri=https://&amp;lt;your_key_vault_name&amp;gt;-vault.vault.azure.net/secrets/&amp;lt;your_secret_name&amp;gt;/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the Function App to successfully talk with Azure Key Vault and retrieve the secret, you’ll also need to create a managed identity. You can &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references" rel="noopener noreferrer"&gt;find details about that process here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Migrating a custom script to Azure Functions is a fairly straightforward process especially if you’re able to reuse a lot of your original code. In my case, it allowed me to expose the local script functionality to anyone and any app. While this particular function is publicly accessible, it’s important to mention that you can also &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/security-concepts" rel="noopener noreferrer"&gt;secure your functions&lt;/a&gt; as needed.&lt;/p&gt;

&lt;p&gt;Is that the end of the story? Not for me. I also needed to create a Power Automate flow to consume the data from the function and update a data store. That’s a subject for &lt;a href="https://dev.to/danwahlin/use-power-automate-to-retrieve-data-from-an-azure-function-for-reporting-2bec"&gt;another post though&lt;/a&gt;.   &lt;/p&gt;

&lt;p&gt;The code shown in this repo can be found here: &lt;a href="https://github.com/DanWahlin/github-repo-stats" rel="noopener noreferrer"&gt;https://github.com/DanWahlin/github-repo-stats&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What’s Next? The next post in this series titled &lt;a href="https://blog.codewithdan.com/use-power-automate-to-retrieve-data-from-an-azure-function-for-reporting/" rel="noopener noreferrer"&gt;Use Power Automate to Retrieve Data from an Azure Function for Reporting&lt;/a&gt; demonstrates how to automate calling the Azure Function and storing the data.&lt;/p&gt;

</description>
      <category>microsoftcloud</category>
      <category>azurefunctions</category>
      <category>azurekeyvault</category>
      <category>powerautomate</category>
    </item>
    <item>
      <title>Use Power Automate to Retrieve Data from an Azure Function for Reporting</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Fri, 04 Nov 2022 03:48:04 +0000</pubDate>
      <link>https://dev.to/danwahlin/use-power-automate-to-retrieve-data-from-an-azure-function-for-reporting-2bec</link>
      <guid>https://dev.to/danwahlin/use-power-automate-to-retrieve-data-from-an-azure-function-for-reporting-2bec</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/danwahlin/migrating-a-local-node-script-to-azure-functions-using-vs-code-225j"&gt;previous post&lt;/a&gt; I showed how to convert a local Node script into an &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/" rel="noopener noreferrer"&gt;Azure Function&lt;/a&gt; so that it can be called from anywhere. While that solution provides a great (and cost effective) way to call the script using HTTP, I also needed to automate the calls and add the data into a spreadsheet for reporting purposes.&lt;/p&gt;

&lt;p&gt;This post explores how to automate the process using &lt;a href="https://powerautomate.microsoft.com/en-us/" rel="noopener noreferrer"&gt;Power Automate&lt;/a&gt;. If you haven’t used Power Automate before it’s part of the Power Platform suite of tools that includes &lt;a href="https://powerapps.microsoft.com/en-us/" rel="noopener noreferrer"&gt;Power Platform&lt;/a&gt;, &lt;a href="https://powerpages.microsoft.com/" rel="noopener noreferrer"&gt;Power Pages&lt;/a&gt;, &lt;a href="https://powervirtualagents.microsoft.com/" rel="noopener noreferrer"&gt;Power Virtual Agents&lt;/a&gt;, and&lt;a href="https://powerbi.microsoft.com/en-us/" rel="noopener noreferrer"&gt;Power BI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Power Automate Flow
&lt;/h2&gt;

&lt;p&gt;The final version of the automation flow I created looks like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbqi1f8m3fr7fwcoevlt.png" alt="Power Automate flow" width="516" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Power Automate Flow&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It performs the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schedules a task to run every day at a specific time.&lt;/li&gt;
&lt;li&gt;Makes an HTTP call to an Azure Function.&lt;/li&gt;
&lt;li&gt;Parses the JSON data returned from the function call.&lt;/li&gt;
&lt;li&gt;Adds each item from the JSON array to an Excel Online spreadsheet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get started, I performed the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Signed in to&lt;a href="https://make.powerautomate.com" rel="noopener noreferrer"&gt;https://make.powerautomate.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Selected my “environment” in the upper-right corner of the screen. You may have multiple environments to choose from if you’re using a work account.&lt;/li&gt;
&lt;li&gt;Selected the &lt;strong&gt;Create&lt;/strong&gt; item in the left menu.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From there I chose &lt;strong&gt;Scheduled cloud flow&lt;/strong&gt; from the available templates:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-3.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aozp9ukcka4vd4p636p.png" alt="Scheduled flow template" width="382" height="173"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Scheduled flow template&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the dialog that appeared I named my flow, defined how often it would run, and then selected the &lt;strong&gt;Create&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-4.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2gxakpa59r94hycteq4.png" alt="Building a flow based on a schedule" width="800" height="507"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Building a flow based on a schedule&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding an HTTP Action
&lt;/h2&gt;

&lt;p&gt;After selecting the &lt;strong&gt;Create&lt;/strong&gt; button, Power Automate automatically added the first step of the flow for me. Since I told it to run every day at a specific time, it configured that information for me:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-5.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jv9br27608o2ndzcvkq.png" alt="The Recurrence action automatically added by Power Automate" width="616" height="103"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Recurrence action automatically added by Power Automate&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The next step in the flow involves calling the Azure Function to retrieve the data needed for reporting. To make that happen, I clicked the &lt;strong&gt;Next step&lt;/strong&gt; button and typed “http” into the search box. I then selected the &lt;strong&gt;HTTP action&lt;/strong&gt; from the options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-6.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d9h5djhinnxi76pimmq.png" alt="Selecting the HTTP action" width="599" height="673"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Selecting the HTTP action&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; The HTTP action is a “premium” feature and requires the proper license. While licensing is beyond the scope of this post, you can find more details in &lt;a href="https://go.microsoft.com/fwlink/?LinkId=2085130&amp;amp;clcid=0x409" rel="noopener noreferrer"&gt;this document&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After selecting the HTTP action, you can enter the &lt;strong&gt;method&lt;/strong&gt; and &lt;strong&gt;URI&lt;/strong&gt; that should be used for the API call. My scenario was quite simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; GET&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URI:&lt;/strong&gt; &lt;a href="https://my-azure-function-domain/api/getGitHubRepoStats" rel="noopener noreferrer"&gt;https://my-azure-function-domain/api/getGitHubRepoStats&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Azure Function for my HTTP call didn’t require authentication (it has publicly available data) so no authentication was needed. Nice and simple. It also didn’t require any specialized headers or queries. In cases where you have to do something more involved, you can learn more about the various options at &lt;a href="https://learn.microsoft.com/en-us/power-automate/desktop-flows/actions-reference/web" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/power-automate/desktop-flows/actions-reference/web&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Parse JSON Action
&lt;/h2&gt;

&lt;p&gt;After entering my method and URI into the HTTP action, I needed a way to access the JSON data returned from the Azure Function and parse it. To handle that I selected the &lt;strong&gt;New step&lt;/strong&gt; button, searched for “json”, and selected the &lt;strong&gt;Parse JSON&lt;/strong&gt; action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-7.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh58tkg5wjmtoqof4psa.png" alt="Selecting the Parse JSON action" width="600" height="693"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Selecting the Parse JSON action&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once the Parse JSON action dialog appeared I performed the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the &lt;strong&gt;Content&lt;/strong&gt; property and pick &lt;strong&gt;Body&lt;/strong&gt; from the options. I want to parse the body of the message returned from the Azure Function call.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Generate from sample&lt;/strong&gt; button and enter the JSON returned from the Azure Function call. This automatically generates a schema and adds it to the &lt;strong&gt;Schema&lt;/strong&gt; property of the Parse JSON action. That’s a super nice feature as you’ll see in the next action that’s added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-8.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hf8qu2ys2mcxdsvckgz.png" alt="Generating a schema from JSON data in the Parse JSON action" width="600" height="335"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Generating a schema from JSON data in the Parse JSON action&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Apply to each Action
&lt;/h2&gt;

&lt;p&gt;Up to this point you’ve seen how to call an HTTP API and parse the JSON. The next step is to store the data somewhere which means iterating through the JSON array that’s returned from the API call. To handle that you can use the &lt;strong&gt;Control&lt;/strong&gt;  &lt;strong&gt;actions&lt;/strong&gt; provided by Power Automate.&lt;/p&gt;

&lt;p&gt;I selected the &lt;strong&gt;Next step&lt;/strong&gt; button again, typed “apply” in the search box, and selected the&lt;a href="https://learn.microsoft.com/en-us/power-automate/apply-to-each" rel="noopener noreferrer"&gt;&lt;strong&gt;Apply to each action&lt;/strong&gt;&lt;/a&gt;. How did I know to select that? The first time I used Power Automate awhile back I didn’t know, so I had to resort to my favorite search engine. But, once you know about it it’s easy to find and use.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Apply to each action&lt;/strong&gt; dialog will ask you to select an output from the previous step. You can select &lt;strong&gt;Body&lt;/strong&gt; from the Parse JSON options in this case since we want to access the JSON object. That gets us to the data we need and will iterate through each object in the array, but how do we add each object into an Excel spreadsheet or some other type of data store?&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a Connector
&lt;/h2&gt;

&lt;p&gt;I initially wanted to store my data in something called &lt;a href="https://learn.microsoft.com/en-us/power-apps/maker/data-platform/data-platform-intro" rel="noopener noreferrer"&gt;Dataverse&lt;/a&gt; and added a connector to that. However, the person consuming the data wanted it in Excel, so I ended up adding a connector to &lt;strong&gt;&lt;a href="https://learn.microsoft.com/en-us/connectors/excelonlinebusiness/" rel="noopener noreferrer"&gt;Excel Online (Business)&lt;/a&gt;&lt;/strong&gt; as well. To do that, I selected the &lt;strong&gt;Add an action&lt;/strong&gt; option from the &lt;strong&gt;Apply to each action&lt;/strong&gt; and selected &lt;strong&gt;Excel Online (Business)&lt;/strong&gt; from the top options that are shown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-9.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4ofuga1ebxl2f3vxrfy.png" alt="Selecting the Excel Online connector" width="636" height="887"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Selecting the Excel Online connector&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next, I entered the following values:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-10.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5as2uylg3cl7lgpd8zc.png" alt="Entering information for the Excel Online connector" width="600" height="524"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Entering information for the Excel Online connector&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This uses &lt;strong&gt;OneDrive for Business&lt;/strong&gt; so I selected a spreadsheet that I created there named &lt;strong&gt;KR 3 FY23.xlsx&lt;/strong&gt; as well as the worksheet name (RepoStats in this example). The values I wanted to store from each object found in the JSON array were then picked. Because a schema was created in the previous Parse JSON step, you can pick the JSON properties you want to assign to your Excel columns for each row. That’s the beautify of having the Parse JSON action generate a schema as mentioned earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating and Testing the Flow
&lt;/h2&gt;

&lt;p&gt;All of the steps needed for my particular scenario are now defined and we’re ready to validate the flow and test it. That can be done by selecting the &lt;strong&gt;Flow checker&lt;/strong&gt; (to validate) and &lt;strong&gt;Test&lt;/strong&gt; (to try it out) options respectively in the upper-right toolbar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-11.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx9sa07wgoi27lw9k4zfb.png" width="505" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Flow checker&lt;/strong&gt; will display any errors or warnings in the flow so that you can fix them. The &lt;strong&gt;Test&lt;/strong&gt; option allows you to manually start the flow to try it out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-12.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1iwgydao8ktmqyw30vst.png" alt="Testing a flow" width="322" height="206"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Testing a flow&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After testing it, you can go to the test run and if the flow ran successfully you’ll see a message at the top (or an error if it failed):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.codewithdan.com/wp-content/uploads/2022/10/image-13.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitwpnu69r24q7wpfqv98.png" alt="View the result of a flow run" width="698" height="396"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;View the result of a flow run&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can drill down into each action to see the what happened and the data that was involved for each action.&lt;/p&gt;

&lt;p&gt;Once the flow was ready to go, I let it run so that the spreadsheet is updated everyday. Someone else within my organization connects to the spreadsheet and brings it into a &lt;a href="https://powerbi.microsoft.com/en-us/" rel="noopener noreferrer"&gt;Power BI dashboard&lt;/a&gt; that we can all view.&lt;/p&gt;

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

&lt;p&gt;While I could’ve written a custom app to perform these different steps, by using Power Automate I was able to quickly schedule the functionality I needed and convert the JSON data into rows within Excel all without writing a single line of code. Although this is a fairly straightforward and arguably simple example, it still saved me a ton of time going this route. With more complex flows that time savings is multiplied and there’s the added benefit of giving other people within your organization permissions to edit the flow as well – even if they’re not a developer.&lt;/p&gt;

&lt;p&gt;If you haven’t tried out Power Automate, create a &lt;a href="https://powerautomate.microsoft.com/en-us/#home-signup" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; and give it a spin. There are infinite tasks that can be automated using it!&lt;/p&gt;

</description>
      <category>microsoftcloud</category>
      <category>powerplatform</category>
      <category>powerautomate</category>
      <category>azurefunctions</category>
    </item>
    <item>
      <title>Simplifying Front-End State Management with Observable Store</title>
      <dc:creator>Dan Wahlin</dc:creator>
      <pubDate>Thu, 21 Mar 2019 22:47:35 +0000</pubDate>
      <link>https://dev.to/danwahlin/simplifying-front-end-state-management-with-observable-store-1jjp</link>
      <guid>https://dev.to/danwahlin/simplifying-front-end-state-management-with-observable-store-1jjp</guid>
      <description>

&lt;p&gt;I admit it - I think the use of some front-end JavaScript state management patterns has gotten out of control. When you’re spending a significant amount of time writing code (and often a lot of it) to handle application state or relying on a scaffolding tool that generates 100s or even 1000s of lines of code then it’s time to take a step back and ask, “Do I really need all of this?”. While you’re at it you might also ask, “What can I do to simplify my code and bring some sanity back to it?”.&lt;/p&gt;

&lt;p&gt;Rather than ranting about my views on keeping software simple, choosing the right tool for the right job, the importance of considering maintenance costs, challenges that more complex patterns present when contractors or new hires are involved, and more, let me get right to the point:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;I think front-end state management needs a big dose of simplicity!&lt;/strong&gt;
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;After hearing from many people and working on projects myself, I was frustrated with some of the state management options out there and decided to experiment with a simple solution that eventually became a project I call &lt;strong&gt;Observable Store&lt;/strong&gt;. It turns out several people had a similar idea which was refreshing to see (there are a few similarly named projects on Github and npm).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you want my more opinionated view on state management complexity you can jump down to &lt;strong&gt;My Two Cents on State Management Complexity&lt;/strong&gt; in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Birth of Observable Store
&lt;/h2&gt;

&lt;p&gt;One of the perks of my job is that I get to work with a lot of developers at companies around the world. This comes in the form of &lt;a href="https://codewithdan.com/products/productType/training"&gt;architecture work, training, mentoring&lt;/a&gt;, talking with people at conferences, meetups, &lt;a href="https://www.apexsystems.com/Events/Pages/CompletedEvents.aspx?filter=wahlin"&gt;webinars&lt;/a&gt;, and more. I’ve had many conversations about various state management options and listened to stories about what has worked and what hasn’t. One common comment I’ve continually heard is, “I wish there was a more simple way to handle state management in my front-end apps”.&lt;/p&gt;

&lt;p&gt;As I’ve talked one on one with other architects and developers, helped people with their projects, and worked on my own, I’ve often asked, “What is it that you really want in a state management solution?”. Here are main goals that came out of asking that question:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Single source of truth&lt;/li&gt;
&lt;li&gt; State is read-only/immutable&lt;/li&gt;
&lt;li&gt; Provide state change notifications to any subscriber&lt;/li&gt;
&lt;li&gt; Track state change history&lt;/li&gt;
&lt;li&gt; Minimal amount of code required&lt;/li&gt;
&lt;li&gt; Works with any front-end library/framework (Angular, React, Vue.js, or anything else that supports JavaScript)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I started playing around with adding these general goals/concepts into a simple library about 1 1/2 years ago and ultimately developed something I now call &lt;a href="https://www.npmjs.com/package/@codewithdan/observable-store"&gt;Observable Store&lt;/a&gt;. I use it for any front-end projects I work on (React, Vue.js, Angular or others) that need a state management solution. Observable Store satisfies the goals mentioned above but does so in an extremely simple way. The code for the library is only around 220 lines total since the “power” it provides comes from using &lt;a href="https://rxjs.dev/"&gt;RxJS Subjects and Observables&lt;/a&gt;. In fact, Observable Store only has 1 dependency – RxJS.&lt;/p&gt;

&lt;p&gt;So why consider Observable Store? If you’re interested in achieving any of the goals shown earlier then Observable Store provides an extremely simple way to achieve those goals. You instantly get a single store that can be referenced throughout your app, state that is immutable (good for change detection in libraries/frameworks), state history tracking, and a way to subscribe to store changes. Plus, Observable Store can be used with any JavaScript library or framework. You’re not locked into anything – except using JavaScript.&lt;/p&gt;

&lt;p&gt;So how do you get started with Observable Store? Here’s a quick overview.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Observable Store
&lt;/h2&gt;

&lt;p&gt;To get started with observable store you simply &lt;strong&gt;npm install&lt;/strong&gt; it in your project (Angular, React, Vue.js, or any JavaScript project):&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;    npm &lt;span class="nb"&gt;install&lt;/span&gt; @codewithdan/observable-store
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;From there you create a service class that extends &lt;strong&gt;ObservableStore&lt;/strong&gt;. If you’re working with TypeScript you can use a generic to pass the shape of the data that gets stored in the store (pass a class or interface). TypeScript isn’t required though and it &lt;a href="https://github.com/DanWahlin/Observable-Store#using-observable-store-with-react"&gt;works fine with ES2015&lt;/a&gt; (or even ES5) as well.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Optionally define what gets stored in the observable store&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StoreState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nl"&gt;selectedCustomer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nl"&gt;selectedOrder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Extend ObservableStore and optionally pass the store state&lt;/span&gt;
    &lt;span class="c1"&gt;// using TypeScript generics (TypeScript isn't required though)&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CustomersService&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ObservableStore&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StoreState&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;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Pass initial store state (if desired). Want to track all&lt;/span&gt;
        &lt;span class="c1"&gt;// changes to the store? Set trackStateHistory to true.&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialStoreState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;trackStateHistory&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now add any functions to your class to retrieve data from a data store and work with the data. Call &lt;strong&gt;setState()&lt;/strong&gt; to set the state in the store or &lt;strong&gt;getState()&lt;/strong&gt; to retrieve state from the store. When setting the state you can pass an action name which is useful when tracking &lt;a href="https://github.com/DanWahlin/Observable-Store#using-observable-store-with-react"&gt;state changes and state history&lt;/a&gt;.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&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;Observable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'rxjs'&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;ObservableStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'@codewithdan/observable-store'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CustomersService&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ObservableStore&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StoreState&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;constructor&lt;/span&gt;&lt;span class="p"&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;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
                &lt;span class="na"&gt;selectedCustomer&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="na"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
                &lt;span class="na"&gt;selectedOrder&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="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;trackStateHistory&lt;/span&gt;&lt;span class="p"&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="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Get state from store&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="o"&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;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;customers&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;customers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Return RxJS Observable&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customers&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="c1"&gt;// call server and get data&lt;/span&gt;
                &lt;span class="c1"&gt;// assume async call here that returns Observable&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;asyncData&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;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Get state from store&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&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;getState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Set state in store&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;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
                          &lt;span class="s1"&gt;'add_customer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Get state from store&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&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;getState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Set state in store&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;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
                          &lt;span class="s1"&gt;'remove_customer'&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;As the store state changes, any part of the application can be notified by subscribing to the store’s &lt;strong&gt;stateChanged&lt;/strong&gt; event. In this example changes made to the store by CustomersService will be received which provides a nice way to listen to a “slice” of the overall store quite easily.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;
    &lt;span class="c1"&gt;// Subscribe to the changes made to the store by &lt;/span&gt;
    &lt;span class="c1"&gt;// CustomersService. Note that you'll want to unsubscribe&lt;/span&gt;
    &lt;span class="c1"&gt;// when done.&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;customersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stateChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&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;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&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 because the store state is immutable, a &lt;strong&gt;stateChanged&lt;/strong&gt; subscriber will always get a “fresh” object back which works well with detecting state/data changes across libraries/frameworks. Because RxJS observables are used behind the scenes you can use all of the great operators that RxJS provides as well.&lt;/p&gt;

&lt;p&gt;If you need to listen to all changes made to the store you can use the &lt;strong&gt;&lt;a href="https://github.com/DanWahlin/Observable-Store#store-api"&gt;globalStateChanged&lt;/a&gt;&lt;/strong&gt; event (thanks to &lt;a href="https://github.com/mickeypuri"&gt;Mickey Puri&lt;/a&gt; for this contribution):&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;
    &lt;span class="c1"&gt;// Subscribe to all store changes, not just the ones triggered&lt;/span&gt;
    &lt;span class="c1"&gt;// by CustomersService&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;customersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalStateChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// access anything currently in the store here&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can even listen to a specific slice of the store (customers and orders for example) by supplying a &lt;strong&gt;&lt;a href="https://github.com/mickeypuri"&gt;stateSliceSelector&lt;/a&gt;&lt;/strong&gt; function.&lt;/p&gt;

&lt;p&gt;To handle orders, you can create another class that extends &lt;strong&gt;ObservableStore&lt;/strong&gt; and add the order related functionality in it. By breaking the functionality out into separate classes you can achieve single responsibility (the “S” in SOLID) while still having only one store backing the entire application.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Extend ObservableStore&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;OrdersService&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ObservableStore&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StoreState&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;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Define that we want to track changes that this object&lt;/span&gt;
        &lt;span class="c1"&gt;// makes to the store&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;trackStateHistory&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Both &lt;strong&gt;CustomersService&lt;/strong&gt; and &lt;strong&gt;OrdersService&lt;/strong&gt; share the same store (as do all classes that extend ObservableStore in your application).&lt;/p&gt;

&lt;p&gt;The Observable Store &lt;a href="https://github.com/DanWahlin/Observable-Store#store-api"&gt;API&lt;/a&gt; and &lt;a href="https://github.com/DanWahlin/Observable-Store#store-api"&gt;settings&lt;/a&gt; are simple to learn and you can get it up and running in no time at all. You can find examples of using it with Angular and React apps (I’m hoping to add a Vue.js example in the near future) in the &lt;a href="https://github.com/DanWahlin/Observable-Store"&gt;Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Is Observable Store the answer to keeping state management simple in front-end applications? It’s one potential solution that has worked well for my company and several other companies/developers who are using it. I’ve been using it privately for over a year now and really enjoy the simplicity it brings to the table. If you try it out or have questions about it feel free to leave a comment below or in the &lt;a href="https://github.com/DanWahlin/Observable-Store/issues"&gt;Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Two Cents on State Management Complexity
&lt;/h2&gt;

&lt;p&gt;I mentioned toward the beginning of this post that I didn’t want to get into “my” opinion on state management since I prefer to focus on potential solutions rather than focusing on problems. I’m just one guy after all that has an opinion that some may agree with and some definitely will disagree with. Having said that, many people ask my opinion about this particular subject so here’s a quick summary of where I stand.&lt;/p&gt;

&lt;p&gt;I think we often get caught up in the “group think” mode of developing software (something that I’m guilty of as well on occasion) and that results in great things and a lot of not so great things spreading like fire across the developer community. Because a concept or pattern is “popular” or “everyone is using it” we gravitate to it without digging in and considering if it’s the best way to go for our specific application scenario, if it’s actually necessary, and the pros/cons it brings to the team or project. It feels like a “sheep off the cliff” mentality in some cases. I recently &lt;a href="https://medium.com/@amcdnl/the-future-of-javascript-state-management-is-less-state-management-ba1d97b99308"&gt;came across a post&lt;/a&gt; that echos a lot of my thoughts on the “state” of front-end state management complexity.&lt;/p&gt;

&lt;p&gt;As I’ve worked with various companies around the world over the years, talked with developers at conferences, and interacted with people online, one of the main “gripes” I keep hearing can be summed up as, “Front-end state management complexity is killing us!”. I also hear, “I can’t believe how much code is added to our application to follow pattern X”, or “We’re using technology X and Y at work across teams and can’t share our state management code between them!”.&lt;/p&gt;

&lt;p&gt;In all fairness, some of the patterns that are available like Redux provide a lot of value. For example, consistency for a team, insight into the flow of data, better debugging in some cases, and more. &lt;strong&gt;I don’t think there’s any dispute there so I want to make that clear&lt;/strong&gt;. Many people are using some of the different font-end state management patterns very successfully especially with larger teams and a lot of moving parts. So what’s the problem?&lt;/p&gt;

&lt;p&gt;For starters, if everyone on a team doesn’t understand a given pattern well, then they’re copying and pasting code or using some type of scaffolding tool without really understanding what’s going on and why they’re doing it. As the application’s complexity grows they feel more and more lost. This often applies to projects that bring in contractors, new hires, or developers that may not work solely in the front-end world. But, it applies to pure front-end developers too I’ve found.&lt;/p&gt;

&lt;p&gt;An argument can be made that anyone using a pattern without really understanding it needs to take time to learn the pattern better, and I think that’s a valid point. But, when someone didn’t choose the pattern used in a project and deadlines are looming, they don’t have much of a choice but to push through it even if they don’t fully understand what’s going on. Plus, I think there’s also an argument to be made that if a pattern requires that much time and code to learn then maybe it’s worth considering if it’s the best way to go in the first place? Keep in mind I’m only talking about state management here. We still have the rest of the application to worry about as well.&lt;/p&gt;

&lt;p&gt;In addition to understanding a pattern well, can you use the same code between different front-end JavaScript technologies and does the code look the same? For example, React has Redux, Angular has NgRx (Redux + RxJS), Vue.js has Vuex, and so on. That may not be an issue for you, but it is for several companies I work with because they don’t want to maintain different implementations of the same overall pattern.&lt;/p&gt;

&lt;p&gt;For the question, “Can you use the same code between different front-end JavaScript technologies?”, I’m going to say the answer to that is a definite “No!” – sharing state management code often isn’t an option in the majority of scenarios I’ve seen. The pattern used may be similar in some cases, but the implementations are radically different between libraries/frameworks. If your company isn’t using just one main library/framework for front-end projects that can present a challenge when you’re trying to make projects as consistent as possible (while also letting developers use the technology they prefer).&lt;/p&gt;

&lt;p&gt;There are certainly additional challenges that I can point out with more complex state management options (maintenance challenges, the sheer amount of code added, bundle sizes, team knowledge, etc.) but that’ll do for now. I think it really boils down to using the right tool for the right job and realizing that not everything is a nail that requires a complex hammer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;I think it really boils down to using the right tool for the right job and realizing that not everything is a nail that requires a complex hammer.&lt;/strong&gt;
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;Isn’t it worth considering if the state management pattern itself (whatever it is) may actually be overly complex for a given scenario and that viable alternatives may exist? One size NEVER fits all and there are many applications out there using a complex state management pattern that simply don’t need it at all. I’ve seen it myself many times at companies. For example, an application may perform standard CRUD (Create, Read, Update, Delete) operations directly to a back-end service. Once an operation is complete it’s done. Aside from showing a message to the user there’s nothing else to do from a state perspective. In this simple scenario and many others there’s often no need for a complex state management solution – it would only add unnecessary complexity. Which brings me to 3 of my favorite words: “keep it simple”.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Keep it Simple!&lt;/strong&gt;
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;I truly admire architects and developers that have the wisdom, knowledge, expertise, and ability to keep their application code as simple as possible while still meeting the needs of users. Building good software is hard, and the ability to keep code simple is arguably just as hard. It’s an art and skill that has to be developed over time and in some cases I feel like that skill has been lost. Keeping things as simple as possible yields many positive results in the end – especially when it comes to long-term maintenance.&lt;/p&gt;

&lt;p&gt;This is definitely one of those highly subjective topics I realize, but let me know your *constructive* thoughts on it in the comments. Every situation is different so I’m always interested in hearing different opinions. You can reach out to me on &lt;a href="https://twitter.com/danwahlin"&gt;Twitter&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;Originally posted at &lt;a href="https://blog.codewithdan.com/simplifying-front-end-state-management-with-observable-store"&gt;https://blog.codewithdan.com&lt;/a&gt;&lt;/p&gt;


</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>spas</category>
      <category>statemanagement</category>
    </item>
  </channel>
</rss>
