<?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: Matt Netkow</title>
    <description>The latest articles on DEV Community by Matt Netkow (@dotnetkow).</description>
    <link>https://dev.to/dotnetkow</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%2F168985%2F14cb89ab-d08c-4ee8-9b61-3f657c6c6091.jpg</url>
      <title>DEV Community: Matt Netkow</title>
      <link>https://dev.to/dotnetkow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dotnetkow"/>
    <language>en</language>
    <item>
      <title>DevCon 2025 Workshop: Creating a Document Processing MCP Server</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Fri, 08 Aug 2025 15:09:01 +0000</pubDate>
      <link>https://dev.to/abbyy_dev/devcon-2025-workshop-creating-a-document-processing-mcp-server-3e5b</link>
      <guid>https://dev.to/abbyy_dev/devcon-2025-workshop-creating-a-document-processing-mcp-server-3e5b</guid>
      <description>&lt;p&gt;I recently led an engaging hands-on workshop at ABBYY DevCon 2025. The session, &lt;strong&gt;"Creating a Document Processing MCP Server,"&lt;/strong&gt; was crafted for developers eager to explore advanced workflow automation, focused on constructing a Model Context Protocol (MCP) server tailored for intelligent document processing (IDP) tasks. MCP is one of the hottest technologies in the AI ecosystem, so I was thrilled to give a presentation on its fundamentals. Here’s an overview of the workshop, its objectives, and the interactive exercises participants tackled.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Want to try some hands-on MCP Server exercises? &lt;a href="https://github.com/abbyy/workshop-mcp-abbyy-bank" rel="noopener noreferrer"&gt;Access the workshop materials&lt;/a&gt;.&lt;/strong&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%2Fkgzo46tllyngy2un1zp8.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%2Fkgzo46tllyngy2un1zp8.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A deep dive into MCP servers
&lt;/h2&gt;

&lt;p&gt;The workshop was centered on building an MCP server designed to streamline a typical bank account onboarding process. By integrating a range of cutting-edge tools and techniques, participants learned how to manage tasks including document uploads, data extraction, validation, and final submission.&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%2Ffbyzji5mgtyy2u1ag7z6.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%2Ffbyzji5mgtyy2u1ag7z6.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MCP servers matter
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol, or MCP, is an open standard that bridges large language models (LLMs) with various data sources and tools, enabling developers to design dynamic workflows. The server built during this workshop demonstrated how MCP serves as both an engine and an orchestrator, bringing both determinism and scalability to complex IDP scenarios. Key features of the MCP server covered in the workshop included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document processing automation:&lt;/strong&gt; Eliminate manual data entry through structured workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time insights and validation:&lt;/strong&gt; Verify identity and residency documents on the fly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility:&lt;/strong&gt; Seamlessly integrate additional tools, prompts, and external resources as needed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Hands-on learning experience
&lt;/h2&gt;

&lt;p&gt;My main focus of the workshop was ensuring an interactive experience. I broke the material into manageable sections with practical coding exercises after each one. Participants were able to immediately apply their knowledge, receive feedback, and enhance their understanding incrementally. Here’s a high-level overview of what was covered:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. MCP fundamentals
&lt;/h3&gt;

&lt;p&gt;The session started with an introduction to MCP architecture and its benefits within document processing workflows. Attendees learned how MCP implements transports, clients, and servers to handle requests and orchestrate operations effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Setting up and testing an MCP server
&lt;/h3&gt;

&lt;p&gt;Participants began hands-on by setting up a basic MCP server, verifying its functionality using the &lt;strong&gt;MCP Inspector&lt;/strong&gt; debugging tool. This phase focused on understanding the foundational structure and testing server responses to ensure accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Document processing with ABBYY Document AI API
&lt;/h3&gt;

&lt;p&gt;The workshop progressed into integrating the &lt;a href="https://www.abbyy.com/ai-document-processing/api/" rel="noopener noreferrer"&gt;ABBYY Document AI API&lt;/a&gt; for optical character recognition (OCR). This step highlighted the importance of leveraging robust, domain-specific OCR tools to avoid issues like hallucinated data, common with general-purpose LLMs. Attendees worked on converting utility bill uploads into structured data, demonstrating real-world applications of IDP.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Tool and resource management
&lt;/h3&gt;

&lt;p&gt;Exercises included defining and implementing server tools to process and validate user data. Participants learned about incorporating a &lt;strong&gt;“human-in-the-loop”&lt;/strong&gt; approach, ensuring secure and effective operations when handling sensitive data.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Extending MCP with Claude integration
&lt;/h3&gt;

&lt;p&gt;Expanding the server's capabilities, participants explored integration with &lt;a href="https://www.abbyy.com/blog/large-language-models/" rel="noopener noreferrer"&gt;Claude Desktop&lt;/a&gt; for creating prompts and guiding workflows. This step allowed for experimenting with adaptive responses, making workflows more dynamic and user focused.&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%2Fexa9y9ijd3shdqhoe6y6.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%2Fexa9y9ijd3shdqhoe6y6.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final workflow: Bank account onboarding
&lt;/h2&gt;

&lt;p&gt;At the end of the workshop, we had built a complete bank account opening workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;From Claude Desktop, the user enters a prompt:&lt;br&gt;
&lt;em&gt;"Help me open a bank account with ABBYY Bank."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The LLM, connecting to the MCP server, recognizes that the user needs to upload a utility bill.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user uploads a utility bill and the MCP server extracts its data using the Document AI API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the info looks correct, the user confirms that the Server can submit the application to the bank.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The LLM responds:&lt;br&gt;
&lt;em&gt;"Excellent! Your bank account application has been successfully submitted to ABBYY Bank. They'll be in touch with next steps."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fnw30dn8jxr70a13wx6f8.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%2Fnw30dn8jxr70a13wx6f8.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Explore the workshop at your own pace
&lt;/h2&gt;

&lt;p&gt;While the live session provided a vibrant and collaborative atmosphere, anyone interested can take the workshop independently. To facilitate this, all code and step-by-step instructions are available in a dedicated GitHub repository:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/abbyy/workshop-mcp-abbyy-bank" rel="noopener noreferrer"&gt;Access the workshop materials&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository, available in &lt;strong&gt;Python or TypeScript&lt;/strong&gt;, includes everything needed to build and test MCP-based workflows, from environment setup to live debugging tools and reference exercises. Developers can also experiment with the next steps suggested in the workshop, such as incorporating new validation types or extending the server for additional document workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Share your journey
&lt;/h2&gt;

&lt;p&gt;If you’re ready to learn MCP fundamentals and how to incorporate intelligent document processing, the &lt;strong&gt;"Creating a Document Processing MCP Server"&lt;/strong&gt; workshop is an excellent place to start. Explore the repository, complete the exercises, and bring your knowledge into real-world projects.&lt;/p&gt;

&lt;p&gt;We'd love to hear your feedback and see the innovations you build with MCP! Let us know when you've completed the workshop by tagging me &lt;strong&gt;&lt;a href="https://x.com/dotnetkow" rel="noopener noreferrer"&gt;@dotNetkow&lt;/a&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;a href="https://x.com/ABBYY_Software" rel="noopener noreferrer"&gt;@ABBYY&lt;/a&gt;&lt;/strong&gt; on LinkedIn or X.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>ocr</category>
    </item>
    <item>
      <title>Choosing OCR Technology: Key Considerations for Software Developers</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Mon, 24 Mar 2025 16:30:11 +0000</pubDate>
      <link>https://dev.to/abbyy_dev/choosing-ocr-technology-key-considerations-for-software-developers-4dkd</link>
      <guid>https://dev.to/abbyy_dev/choosing-ocr-technology-key-considerations-for-software-developers-4dkd</guid>
      <description>&lt;p&gt;When it comes to choosing OCR (Optical Character Recognition) technology, developers have a lot to consider. Since OCR solutions have been around for decades, it’s tempting to think that they are standardized and thus, any of them will do. That couldn’t be farther from the truth: not all OCRs are created equally, so choosing the right one can still be a headache. From the type of models to AI offerings to pricing and community support, many factors play a crucial role in determining the best fit for your project. This article covers key points to keep in mind, including considerations for open source models, limitations of LLMs, and pricing. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Join the waitlist: &lt;a href="https://digital.abbyy.com/code-extract-automate-your-new-must-have-ocr-api-coming-soon/?itm_source=devto" rel="noopener noreferrer"&gt;new OCR API for AI developers coming soon&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Open-source models: Cost effective, but less accurate
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open-source OCR models like Tesseract and PaddleOCR are popular choices among developers due to their accessibility and cost-effectiveness. However, they come with certain limitations: &lt;/li&gt;
&lt;li&gt;Accuracy: Open-source models often have lower accuracy compared to commercial engines. They struggle with handwriting, rotated text, and low-quality images. &lt;/li&gt;
&lt;li&gt;Support for complex documents: These models may not handle complex documents, tables, and charts effectively. &lt;/li&gt;
&lt;li&gt;Continuous optimization: Enhancements to OSS models are at the whim of the community. Maintainers come and go, and their priorities often differ from your project’s needs. Proprietary companies maintain an edge through continuous optimization, leveraging years of practical experience and refined technologies. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open-source OCR models may work for POCs or processing simple documents, but if high-quality, reliable accuracy is a must, they are a no-go. &lt;/p&gt;

&lt;h2&gt;
  
  
  Can LLMs replace OCR? Not so fast
&lt;/h2&gt;

&lt;p&gt;LLMs like GPT-4.5 and other general-purpose AI models are increasingly being used for document processing. The ability to quickly test their OCR abilities by uploading a document through a web UI or chatbot is compelling. However, they also have their challenges: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hallucinations: LLMs often omit significant portions of text, hallucinate content, and fail to output text coordinates.
&lt;/li&gt;
&lt;li&gt;Inconsistencies: They display inconsistent formatting and table extraction, making them less reliable for robust OCR tasks. Results themselves are inconsistent too, meaning you could process the same document ten times and get ten different results. &lt;/li&gt;
&lt;li&gt;Speed and cost: LLM-based extraction can be slow and expensive due to high compute costs. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Due to the unpredictability of inaccuracies in large language models (LLMs), the automation of business processes is hindered. This puts significant burden on the developer to capture errors and code exceptions, feeling like a game of “LLM whack-a-mole.” Downstream, any issues missed would require users to resort to manual corrections. This defeats the purpose of introducing OCR solutions in the first place. &lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing: Cheap may cost you more
&lt;/h2&gt;

&lt;p&gt;Pricing is a critical factor when choosing an OCR solution, but it's not just about the cost. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Support and reliability: A significant benefit of paying for a solution, especially when business-critical processes depend on it, is ready access to the support, advisory, and SLAs are included. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost-effectiveness: Look for solutions that offer a low-cost, pay-as-you-go model, ensuring scalable solutions without unexpected expenses. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Free trials and freemium tiers: Many commercial OCR solutions offer free trials or freemium tiers, allowing developers to test capabilities before committing. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Capability comparisons: Many solutions, especially those from hyperscalers like Microsoft or AWS, appear cheap up front because they price their OCR capabilities a la carte. When compared to an all-inclusive pricing model, of course it’ll seem cheaper! Review all pricing pages carefully. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When assessing OCR solutions, seek those that provide adequate trial periods, sufficient document processing capacity, and a pay-as-you-go pricing model. &lt;/p&gt;

&lt;h2&gt;
  
  
  Developer support and community
&lt;/h2&gt;

&lt;p&gt;A great product is not enough; comprehensive support and an active community are essential. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Documentation and SDKs: Ensure the OCR solution provides detailed documentation, SDKs, and sandbox environments to streamline integration and optimize solutions. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Community engagement: The OCR solution should have an active and friendly developer community to turn to if needed. The best encourage you to exchange ideas, get expert guidance, and enhance your OCR implementations. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The OCR world is more complex than it looks on the surface. It’s a solved problem, until you need real-world accuracy, reliability, and robust capabilities. To ensure project success, look for a strong company and community-backed solution. &lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing ABBYY’s purpose-built Document OCR API for Developers (Coming soon)
&lt;/h2&gt;

&lt;p&gt;Choosing the right OCR solution involves balancing the above factors to meet your specific needs. If your project is business critical, then ABBYY’s new Document AI platform warrants a look. &lt;/p&gt;

&lt;p&gt;ABBYY’s upcoming Document AI API is a developer-friendly, purpose-built OCR service designed for seamless integration into AI-powered business process automation workflows. It efficiently converts unstructured business documents into structured JSON with exceptional accuracy and reliability, equipping your business solutions and application for success. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://digital.abbyy.com/code-extract-automate-your-new-must-have-ocr-api-coming-soon?itm_source=devto" rel="noopener noreferrer"&gt;Join the Waitlist&lt;/a&gt; &lt;/p&gt;

</description>
      <category>ocr</category>
      <category>ai</category>
    </item>
    <item>
      <title>Gating Paid Features with PropelAuth’s Role Mappings</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Tue, 26 Nov 2024 21:40:37 +0000</pubDate>
      <link>https://dev.to/propelauth/gating-paid-features-with-propelauths-role-mappings-16io</link>
      <guid>https://dev.to/propelauth/gating-paid-features-with-propelauths-role-mappings-16io</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%2F841cv0bcxzm244lxxzyd.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%2F841cv0bcxzm244lxxzyd.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ve configured your SaaS pricing plans in your payment provider of choice but need a way to tie them to your product’s roles and permissions since several features are behind a paywall. What now?&lt;/p&gt;

&lt;p&gt;Enter PropelAuth’s &lt;a href="https://docs.propelauth.com/overview/authorization/role-mappings?ref=propelauth.com" rel="noopener noreferrer"&gt;Role Mappings&lt;/a&gt; feature, which allows the creation of multiple role and permission configurations. You can use role mappings to ensure that your product's roles and permissions can be changed as users switch between paid plans.&lt;/p&gt;

&lt;p&gt;In this post, we’ll create free, paid, and enterprise pricing plans for a simple ticket system modeled after Zendesk, a popular ticketing and help center platform. Here’s an overview of roles and permissions that we’ll implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have three roles: Owner, Manager, and Agent.&lt;/li&gt;
&lt;li&gt;When users write to the Support team, a new issue is created. All team members can view and manage issues.&lt;/li&gt;
&lt;li&gt;Owners and managers on the free plan can view reports covering issue resolution, but users on the Paid plan who are Owners or Managers can export them only.&lt;/li&gt;
&lt;li&gt;Only Owners can access and change Billing across any plan since credit card details are involved.&lt;/li&gt;
&lt;li&gt;Only Enterprise plans have the “live agent activity” feature, which shows agent statuses across all channels, how many conversations they are working on, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To implement this use case, we’ll create three pricing plans using PropelAuth’s &lt;a href="https://docs.propelauth.com/overview/authorization/role-mappings?ref=propelauth.com" rel="noopener noreferrer"&gt;Role Mappings&lt;/a&gt; feature, which defines the relationship between roles and permissions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Creating all of the pricing plans in this tutorial requires access to multiple role mappings. &lt;a href="https://www.propelauth.com/pricing?ref=propelauth.com" rel="noopener noreferrer"&gt;Upgrade to a paid plan now&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s a diagram demonstrating the domain:&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%2F9khkcfqb0g5noj463lex.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%2F9khkcfqb0g5noj463lex.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Steps
&lt;/h2&gt;

&lt;p&gt;Follow these steps to implement free, paid, and enterprise pricing plans in PropelAuth.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Rename Roles
&lt;/h3&gt;

&lt;p&gt;By default, PropelAuth provides Owner, Admin, and Member roles. To match the ticketing system nomenclature, rename Admin to Manager and Member to Agent. In the PropelAuth dashboard, navigate to Roles &amp;amp; Permissions → Roles. Click the gear icon next to each and choose Edit Name.&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%2F4a5gy42neqk0xnposaqn.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%2F4a5gy42neqk0xnposaqn.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Define Permissions
&lt;/h3&gt;

&lt;p&gt;Permissions in a ticket system will vary. Here are the permissions we’ll create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage Billing&lt;/li&gt;
&lt;li&gt;Manage Issues, Read Issues&lt;/li&gt;
&lt;li&gt;Read Reports, Export Reports&lt;/li&gt;
&lt;li&gt;View Agent Activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create a new permission, navigate to Roles &amp;amp; Permissions → Permissions. Click the Add Permission button. In this example, we’ll create the “Manage Billing” permission.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We recommend using a common structure for the IDs, such as “feature::action” where “action” is one of the CRUD operations (create, read, update, delete).&lt;/p&gt;
&lt;/blockquote&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%2Fr15205axck6fmqo1z0t0.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%2Fr15205axck6fmqo1z0t0.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen, click to disable “Enable for all roles” then select the Owner role only since other employees shouldn’t have access to company credit card data. On the last screen, click Add Permission to create 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%2Fsghl2thdrdrt3hx67p37.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%2Fsghl2thdrdrt3hx67p37.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Define Role Mappings
&lt;/h3&gt;

&lt;p&gt;Most of the magic in this pricing plan use case is implemented with role mappings, which define the relationship between roles and permissions. Head over to Roles &amp;amp; Permissions → Mappings. First, click the three dots on the right side to rename the default role mapping to “Free,” then click Rename Mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role Mapping: Free Plan
&lt;/h3&gt;

&lt;p&gt;Click into the Free plan role mapping, then choose the Mapping → Custom tab. Disable access to &lt;code&gt;Export Reports&lt;/code&gt; on all plans since that should be a paid feature. Also, ensure owners only have &lt;code&gt;Manage Billing&lt;/code&gt; permission.&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%2Fzsjvk2t5imf7htjjo23d.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%2Fzsjvk2t5imf7htjjo23d.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Paid Plan Mapping
&lt;/h2&gt;

&lt;p&gt;Next, create a new Role Mapping named “Paid.” Since the paid plan is mostly the same as the free one, choose the “Free” plan as the mapping to duplicate. On the next screen, keep the PropelAuth permissions the same. Next, enable &lt;code&gt;Export Reports&lt;/code&gt; for Owners and Managers since that is a paid feature.&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%2Fyakffurheosdhe0d0ob0.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%2Fyakffurheosdhe0d0ob0.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Plan Mapping
&lt;/h2&gt;

&lt;p&gt;The final role mapping we need is the Enterprise plan, which represents features only large businesses need. Create another new Role Mapping and copy the Paid plan mapping. In this example, our application has a “live agent activity” feature, which shows agent statuses across all channels, how many conversations they are working on, and more. This is available only to Enterprise plan users, so enable &lt;code&gt;View Agent Activity&lt;/code&gt; permission for Owners and Managers. In application code, we can restrict Managers to only view their team's agents.&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%2Fqsupyeaax7dyg3etmx7g.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%2Fqsupyeaax7dyg3etmx7g.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all three pricing plans implemented, there’s one last step.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Subscribe Organizations to Role Mapping(s)
&lt;/h3&gt;

&lt;p&gt;In order for the above role mappings to take effect, we need to assign them to organizations. &lt;a href="https://docs.propelauth.com/getting-started/basics/organizations?ref=propelauth.com" rel="noopener noreferrer"&gt;Organizations&lt;/a&gt; in PropelAuth are groups of users that use your product together, such as companies, teams, etc. Let’s assign the fictional “NetkoOrg” company to the Enterprise pricing plan. Navigate to the Enterprise Role Mapping (Roles &amp;amp; Permissions → Mappings → Enterprise) then select the Subscriptions tab.&lt;/p&gt;

&lt;p&gt;Click the Add Subscriptions button, choose the Environment, then select the organization to assign to the Enterprise role mapping.&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%2Fammcxf7uenvksrqq5nw2.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%2Fammcxf7uenvksrqq5nw2.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back on the main Role Mappings page, we can now see that one organization has been assigned to the Enterprise Role Mapping.&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%2Fohwx6yjwvq5f53cdkce1.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%2Fohwx6yjwvq5f53cdkce1.png" alt="Gating Paid Features with PropelAuth’s Role Mappings" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In application code, a simple permission check will allow only Owners or Managers in an organization tied to the Enterprise role mapping to access the agent activity feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const user = await validateAccessTokenAndGetUserClass(authorizationHeader);

// Owner and Manager: true
// Agent: false
// Also permitted due to being an Enterprise organization
user.hasPermission(orgId, "agent_activity::view");

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

&lt;/div&gt;



&lt;p&gt;We’ve created a robust pricing plan structure using PropelAuth’s Role Mappings feature in just a few steps. Now, let’s look at common real-world scenarios you and/or your users may encounter and how to handle them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenarios
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Owner upgrades to Paid Plan
&lt;/h3&gt;

&lt;p&gt;The Owner of an organization is on the Free plan but wants access to additional features. After they upgrade to the Paid plan, grant their org access by changing their role mapping to “Paid” using &lt;code&gt;subscribeOrgToRoleMapping&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth.subscribeOrgToRoleMapping({
  orgId: "1189c444-8a2d-4c41-8b4b-ae43ce79a492",
  customRoleMappingName: "Paid"
})

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

&lt;/div&gt;



&lt;p&gt;Afterward, check that they have permission to &lt;code&gt;Export Reports&lt;/code&gt;, which will return &lt;code&gt;true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before - Owner and Manager: false
// After - Owner and Manager: true
user.hasPermission(orgId, "reports::export");

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Owner upgrades to Enterprise Plan
&lt;/h3&gt;

&lt;p&gt;Similarly, the organization Owner might upgrade to the Enterprise plan to access features unique to enterprises, such as the agent activity viewer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth.subscribeOrgToRoleMapping({
  orgId: "1189c444-8a2d-4c41-8b4b-ae43ce79a492",
  customRoleMappingName: "Enterprise"
})

// Before - Owner and Manager: false
// After - Owner and Manager: true
user.hasPermission(orgId, "agent_activity::view");

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;PropelAuth's Role Mappings feature makes it super easy to set up different pricing plans with custom permissions. In this post, we created three roles (Owner, Manager, and Agent) and gave them varied access across Free, Paid, and Enterprise tiers. The best part? Role Mappings are flexible, so you’re now equipped with the knowledge to customize them for your app!&lt;/p&gt;

&lt;p&gt;You'll love how simple it is to set up pricing plans with PropelAuth's easy-to-use roles and permissions system. As your app grows, you can rest easy knowing that managing user access and features will be a breeze.&lt;/p&gt;

</description>
      <category>rbac</category>
      <category>rolemappings</category>
      <category>propelauth</category>
    </item>
    <item>
      <title>PropelAuth Python v4 Release</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Mon, 25 Nov 2024 22:03:17 +0000</pubDate>
      <link>https://dev.to/propelauth/propelauth-python-v4-release-5g1c</link>
      <guid>https://dev.to/propelauth/propelauth-python-v4-release-5g1c</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%2F6304h1kpiiytptduv0os.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%2F6304h1kpiiytptduv0os.png" alt="PropelAuth Python v4 Release" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, we are excited to release a new version of our base Python library, as well as releases of our framework-specific libraries for FastAPI, Flask, and Django Rest Framework.&lt;/p&gt;

&lt;p&gt;Let’s jump in to some of the larger changes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Typing Support (breaking change)
&lt;/h2&gt;

&lt;p&gt;If you’ve used our Python libraries before, the type hinting left a lot to be desired. In our latest release, we now have type hints for all requests as well as datatypes for all responses.&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%2Fd1xtaometlkr6akl1rq4.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%2Fd1xtaometlkr6akl1rq4.png" alt="PropelAuth Python v4 Release" width="612" height="250"&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%2Fl5s88030hxnvxhgb2ead.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%2Fl5s88030hxnvxhgb2ead.png" alt="PropelAuth Python v4 Release" width="658" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NOTE: This will break specifically if you were previously unpacking (using the &lt;code&gt;**&lt;/code&gt; operator) on responses. Responses were previously dicts and are now explicit datatypes.&lt;/p&gt;

&lt;p&gt;We have implemented commonly used functions like a key lookup (&lt;code&gt;response["user_id"]&lt;/code&gt; will still work, but &lt;code&gt;response.user_id&lt;/code&gt; is now preferred). We typically try to avoid breaking changes (this is our second in 3 years), but this felt like a pretty narrow problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  User class improvements
&lt;/h2&gt;

&lt;p&gt;For simpler permissions checking, you can now call functions directly on the &lt;code&gt;User&lt;/code&gt; object like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user.has_permission_in_org(orgId, 'can_export_reports')&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user.is_role(orgId, 'Admin')&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user.get_active_org().has_permission('api_key::write')&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These allow you to pass around the &lt;code&gt;User&lt;/code&gt; object instead of needing to refer back to the &lt;code&gt;Auth&lt;/code&gt; object, and it also allows for easier mocking/testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  New APIs
&lt;/h2&gt;

&lt;p&gt;This isn’t specific to our Python library, but we’ve released a lot of new APIs like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.propelauth.com/reference/api/user?ref=propelauth.com#logout-all-user-sessions" rel="noopener noreferrer"&gt;Force logout all user sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.propelauth.com/reference/api/org?ref=propelauth.com#create-saml-connection-link" rel="noopener noreferrer"&gt;Creating a SAML setup link for your customer&lt;/a&gt; (which will let them manage SAML themselves)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.propelauth.com/reference/api/org?ref=propelauth.com#fetch-pending-invites" rel="noopener noreferrer"&gt;Fetching&lt;/a&gt; and &lt;a href="https://docs.propelauth.com/reference/api/org?ref=propelauth.com#revoke-pending-org-invite" rel="noopener noreferrer"&gt;revoking pending invites&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Support for &lt;a href="https://docs.propelauth.com/reference/api/org?ref=propelauth.com#create-org" rel="noopener noreferrer"&gt;legacy_org_id&lt;/a&gt; which can help you migrate from your existing setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the full list in our reference docs &lt;a href="https://docs.propelauth.com/reference/api/getting-started?ref=propelauth.com" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example - Easy feature gating by pricing plan
&lt;/h2&gt;

&lt;p&gt;At PropelAuth, we’ve been fortunate to have a front-row seat to seeing many B2B SaaS companies grow. Auth providers are most important at critical moments in a company's history (initial launch, onboarding your first customer, closing your first enterprise customer, etc.). The most important thing we can do as you grow is to get out of the way.&lt;/p&gt;

&lt;p&gt;That’s why we’re really happy with this FastAPI route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.post("/api/expensive-action")
async def do_expensive_action(user: User = Depends(auth.require_user)):
    org = user.get_active_org()

    if org == None or \
       not org.user_has_permission("can_do_expensive_action"):
        raise HTTPException(status_code=403, detail="Forbidden")

    return do_expensive_action_inner(user, org)

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

&lt;/div&gt;



&lt;p&gt;At first glance, this seems like a pretty simple route, but it has a few important pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.propelauth.com/post/a-practical-guide-to-dependency-injection-with-fastapis-depends?ref=propelauth.com" rel="noopener noreferrer"&gt;dependency injected&lt;/a&gt; &lt;code&gt;User&lt;/code&gt; works with any type of authenticated user - whether it’s password, SSO, SAML, etc.&lt;/li&gt;
&lt;li&gt;With &lt;a href="https://docs.propelauth.com/overview/authorization/role-mappings?ref=propelauth.com" rel="noopener noreferrer"&gt;role mappings&lt;/a&gt;, we can make it so an &lt;code&gt;Admin&lt;/code&gt; of an org on our Free plan can &lt;strong&gt;not&lt;/strong&gt; do the expensive feature, but an &lt;code&gt;Admin&lt;/code&gt; of an org on our Paid plans &lt;strong&gt;can&lt;/strong&gt; do the expensive action.&lt;/li&gt;
&lt;li&gt;We can enforce that programmatically by handling a webhook from our payment provider and setting their role mapping, like so:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.post("/stripe/webhook")
def stripe_webhook(request):
    event = parse_and_validate_stripe_webhook(request)
    org_id = event["org_id"]

    if event["type"] == Events.CUSTOMER_SUBSCRIBED:
        # This can give the users in this organization access to increased
        # permissions, additional roles, and access to more features
        auth.subscribe_org_to_role_mapping(org_id, "Paid Plan")

    elif event["type"] == Events.CUSTOMER_UNSUBSCRIBED:
        auth.subscribe_org_to_role_mapping(org_id, "Free Plan")

    # ...

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We can even give them self-serve access to advanced features like SAML and SCIM by &lt;a href="https://docs.propelauth.com/reference/api/org?ref=propelauth.com#create-saml-connection-link" rel="noopener noreferrer"&gt;programmatically generating a SAML connection URL&lt;/a&gt;, which will guide your user through setting up those features - with specific instructions for each identity provider (Okta, Azure AD, ADFS, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the best part? That same code snippet above will continue to work. Even as our customer’s requirements get more complicated, your code won’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions? Feedback?
&lt;/h2&gt;

&lt;p&gt;We're always looking to improve our libraries and services based on your feedback. If you have any questions about this release or suggestions for future improvements, please don't hesitate to &lt;a href="//mailto:support@propelauth.com"&gt;reach out&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>propelauth</category>
    </item>
    <item>
      <title>Securing a GenAI Service with API Key Validation and Trial Management</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Mon, 18 Nov 2024 22:02:17 +0000</pubDate>
      <link>https://dev.to/propelauth/securing-a-genai-service-with-api-key-validation-and-trial-management-467p</link>
      <guid>https://dev.to/propelauth/securing-a-genai-service-with-api-key-validation-and-trial-management-467p</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%2Frclz1i2m5feijq50868p.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%2Frclz1i2m5feijq50868p.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tutorial will guide you through securing a GenAI service with a free trial and API key validation using Node.js, Express, and &lt;a href="https://propelauth.com/?ref=propelauth.com" rel="noopener noreferrer"&gt;PropelAuth&lt;/a&gt;, a B2B authentication provider. We'll set up a system where users can sign up for a free 7-day trial, upgrade to a paid plan, and use their API key to generate images. The user's API key is validated on each request to generate a new image, and the trial expiration date is checked before the request is permitted.&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%2Fk7vo9el8zo9myl0n99xy.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%2Fk7vo9el8zo9myl0n99xy.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="970"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's dive in and explore the key components of the user signup process, plan upgrades, and image generation workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We’ll Build
&lt;/h2&gt;

&lt;p&gt;We’re building an image generation API similar to Midjourney. To complete this tutorial, we’ll leverage PropelAuth’s &lt;a href="https://docs.propelauth.com/overview/authentication/api-keys?ref=propelauth.com" rel="noopener noreferrer"&gt;API Key Authentication&lt;/a&gt;, &lt;a href="https://docs.propelauth.com/overview/user-management/user-properties?ref=propelauth.com#custom-user-properties" rel="noopener noreferrer"&gt;custom user properties&lt;/a&gt;, and &lt;a href="https://docs.propelauth.com/getting-started/basics/hosted-pages?ref=propelauth.com#user-account-page" rel="noopener noreferrer"&gt;account management&lt;/a&gt; features. We’ll also create an Express API with endpoints to simulate Stripe payment processing and image creation. The endpoints are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stripe Webhook (&lt;code&gt;/webhook/stripe&lt;/code&gt;)&lt;/strong&gt;: Listen for when users upgrade to a paid plan or downgrade to the free plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Create (&lt;code&gt;/api/image/create&lt;/code&gt;)&lt;/strong&gt;: Before generating an image, validate the API key and check that the user has not exceeded the seven-day trial window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find the complete code &lt;a href="https://github.com/PropelAuth/demo-genai-api-keys?ref=propelauth.com" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;. Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the GenAI Company
&lt;/h2&gt;

&lt;p&gt;Begin by signing up for a &lt;a href="https://propelauth.com/?ref=propelauth.com" rel="noopener noreferrer"&gt;free PropelAuth account&lt;/a&gt; if you don’t have one already. After signing up, create a project. A project includes everything you need to set up authentication, like &lt;a href="https://docs.propelauth.com/overview/misc/hosted-pages?ref=propelauth.com" rel="noopener noreferrer"&gt;&lt;strong&gt;UIs&lt;/strong&gt;&lt;/a&gt;, a dashboard for managing your users, transactional emails, and more. In the dashboard, open the Signup page by navigating to the Preview button in the top right corner and choosing Signup.&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%2Fsy4gtadkrr1c2fg6pkyv.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%2Fsy4gtadkrr1c2fg6pkyv.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Signup page is one of several &lt;a href="https://www.notion.so/API-Key-Tutorial-13753e303ce68086a2e9df75f2636eac?pvs=21&amp;amp;ref=propelauth.com" rel="noopener noreferrer"&gt;hosted pages&lt;/a&gt; that PropelAuth provides. Hosted pages are pre-built, customizable web pages such as signup, login, and account that integrate seamlessly with your application. It will look similar to:&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%2Fho9k81h31zvx7gvhzd4g.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%2Fho9k81h31zvx7gvhzd4g.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="508" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need a test user account for this application, so go ahead and sign up! Use any email you can access, including the one you signed up for PropelAuth with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Securing the Image Creation Service
&lt;/h3&gt;

&lt;p&gt;To secure access to our image creation service, we’ll require users to send an API key to an image creation API we’ll define shortly. PropelAuth provides API key management that is out-of-the-box for personal users and organization use cases. Users can create their own API keys from the Personal API Keys page. One less thing for us to build!&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%2Fy7vugmyc68pb7c4aikaj.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%2Fy7vugmyc68pb7c4aikaj.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head back into the PropelAuth dashboard and onto the API Key Settings page. Toggle “Personal API Keys” to ON so users can create API keys themselves.&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%2F452zlw3kn9rnsopx84x5.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%2F452zlw3kn9rnsopx84x5.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let’s add free and paid plans as an additional mechanism for verifying image creation access. This is another thing that PropelAuth can help us with! In the dashboard, navigate to User Properties. These are fields that you can use to store information about your users, such as how they use your product, their profile picture, or, in our case, the plan they are subscribed to.&lt;/p&gt;

&lt;p&gt;Let’s create a custom User Property called &lt;code&gt;plan_name&lt;/code&gt;. Click “Add Custom Property.” We want our users to be able to see their plan from the PropelAuth account page (more on that soon), so choose “Managed by your users,” select the type “Enum,” and enter the name “plan_name.” Once created, set the Enum Values to Free and Paid (&lt;code&gt;Free | Paid&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The user should be able to view their plan at any time. To enable this, toggle “Show in Account Page.” Next, select “Not Required” since we don’t need to collect this property from users. Instead, we’ll set it behind the scenes. Finally, answer “Can Users Edit?” with “No (Read-only)” for obvious reasons— we wouldn’t want users to switch to the Paid plan without paying!&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%2Fzl1ebtzqn37drlb9gr4c.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%2Fzl1ebtzqn37drlb9gr4c.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ve now set up the backend portion of the image creation service. Now, we can create the webhook and API that leverages the API keys and plan name custom user property.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the GenAI Image Creation API
&lt;/h2&gt;

&lt;p&gt;To implement the image creation API, we’ll use Express.js for its simplicity and ease of use. If you have a favorite web API framework, no worries! The concepts covered below should be easy to follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Express App
&lt;/h3&gt;

&lt;p&gt;Here’s a brief overview of setting up the app from scratch. You can also reference &lt;a href="https://github.com/PropelAuth/demo-genai-api-keys?ref=propelauth.com" rel="noopener noreferrer"&gt;the complete code on GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir myapp
$ cd myapp
$ npm init
$ entry point: (src/app.ts)

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

&lt;/div&gt;



&lt;p&gt;Though not required, the reference code repository was configured to use TypeScript, so we’ll configure it here too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, install Express, TypeScript, and Type Definitions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install express typescript ts-node ts-node-dev @types/node @types/express --save-dev

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

&lt;/div&gt;



&lt;p&gt;Next, create a TypeScript config file:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;tsconfig.json&lt;/code&gt; and configure it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compilerOptions": {
    "target": "ES6",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

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

&lt;/div&gt;



&lt;p&gt;Finally, update &lt;code&gt;package.json&lt;/code&gt; to add a &lt;code&gt;dev&lt;/code&gt; command for local debugging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "dev": "ts-node-dev src/app.ts"
}

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

&lt;/div&gt;



&lt;p&gt;Now, we can move on to implementation. Open &lt;code&gt;src/app.ts&lt;/code&gt; to see the basics of the Express app’s setup. The following creates the Express server running on port 3000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express, { Request, Response } from "express";

const app = express();
const port = 3000;

app.listen(port, () =&amp;gt; {
  console.log(`Server is running at http://localhost:${port}`);
});

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a “Stripe” Webhook
&lt;/h3&gt;

&lt;p&gt;The first functionality we need is updating a user’s plan when they’ve upgraded to a Paid plan or downgraded to a Free one. We’d use a payment provider like Stripe to handle payment processing in a real-world app. Here’s how the workflow would work:&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%2F2f9rh1arsg3wkxoi10lf.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%2F2f9rh1arsg3wkxoi10lf.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="73"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Incorporating Stripe or a similar payment provider is outside the scope of this tutorial, but we can simulate it easily. Create a new endpoint called&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/webhook/stripe&lt;/code&gt; that pulls out the user ID and event name from the request body. If a new “subscription” has been created, update the user’s plan to Paid. Otherwise, if it’s been removed (downgraded), change it to “Free.”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.post("/webhook/stripe", async (req: Request, res) =&amp;gt; {
  const event = req.body;
  const userId = event.data.object.metadata.userId;
  switch (event.type) {
    case "customer.subscription.created":
      await updateUserPlan(userId, "Paid");
      break;
    case "customer.subscription.deleted":
      await updateUserPlan(userId, "Free");
      break;
  }

  // Return a response to acknowledge receipt of the event
  res.json({ received: true });
});

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

&lt;/div&gt;



&lt;p&gt;In newer versions of Express, we need to install a separate package, &lt;code&gt;body-parser&lt;/code&gt; , to access the request body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install body-parser

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

&lt;/div&gt;



&lt;p&gt;Import it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import bodyParser from "body-parser";

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

&lt;/div&gt;



&lt;p&gt;Then, create a middleware that parses 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 app = express();
const port = 3000;
const jsonParser = bodyParser.json();

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

&lt;/div&gt;



&lt;p&gt;Next, add the JSON parser to the webhook method signature. Now, &lt;code&gt;req.body&lt;/code&gt; will automatically be parsed and available to us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.post("/webhook/stripe", jsonParser, async (req: Request, res) =&amp;gt; { 
  const event = req.body;
}

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

&lt;/div&gt;



&lt;p&gt;We need to implement the functionality to change a user’s plan. As a reminder, that custom user property is defined in our PropelAuth instance. Fortunately, they provide an easy-to-use SDK.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing PropelAuth
&lt;/h3&gt;

&lt;p&gt;Begin by installing &lt;a href="https://docs.propelauth.com/reference/backend-apis/express?ref=propelauth.com" rel="noopener noreferrer"&gt;PropelAuth’s Express library&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @propelauth/express

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

&lt;/div&gt;



&lt;p&gt;Next, import and call &lt;code&gt;initAuth&lt;/code&gt;, which performs a one-time initialization of the library and verifies our image service’s API key is correct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { initAuth, UserMetadata } from "@propelauth/express";

const auth = initAuth({
  authUrl: process.env.PROPELAUTH_AUTH_URL!,
  apiKey: process.env.PROPELAUTH_API_KEY!,
});

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

&lt;/div&gt;



&lt;p&gt;Wait a minute! We don’t have an &lt;code&gt;apiKey&lt;/code&gt; or &lt;code&gt;authUrl&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Backend API Key
&lt;/h3&gt;

&lt;p&gt;To update a user’s plan or validate a user’s API key, we need to connect our app to our PropelAuth instance. When we make Express library requests to PropelAuth, we’ll authenticate using a dedicated API key. To create one, head back into the PropelAuth dashboard.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to “Integrate your product” → “Backend Integration.”&lt;/li&gt;
&lt;li&gt;Remain in the pre-selected “Test” environment, then click “Create New API Key.”&lt;/li&gt;
&lt;li&gt;Enter a name and click Create.&lt;/li&gt;
&lt;li&gt;Copy the API key immediately since you can only view it once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll also need the Auth URL, the unique URL pointing to your PropelAuth auth server. You can access that at any 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%2Fl0tbaz4ev0thi6vqpsm3.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%2Fl0tbaz4ev0thi6vqpsm3.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure PropelAuth Express Library with Environment Files
&lt;/h3&gt;

&lt;p&gt;Let’s use environment files to share the Auth URL and API Key with the PropelAuth Express library. First, install the popular &lt;code&gt;dotenv&lt;/code&gt; package that loads environment variables from .env into Node.js projects.&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file at the project's root and copy/paste your Auth URL and the API key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# similar values as these
PROPELAUTH_AUTH_URL=https://123.propelauthtest.com
PROPELAUTH_API_KEY=d68422536...

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

&lt;/div&gt;



&lt;p&gt;With those in place, &lt;code&gt;process.env.PROPELAUTH_AUTH_URL&lt;/code&gt; will resolve correctly during the call to &lt;code&gt;initAuth&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const auth = initAuth({
  authUrl: process.env.PROPELAUTH_AUTH_URL!,
  apiKey: process.env.PROPELAUTH_API_KEY!,
});

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update a User’s Plan
&lt;/h3&gt;

&lt;p&gt;The PropelAuth library can authenticate our image creation service now, so the last step is adding the ability to change a user’s plan. Provide the &lt;code&gt;userId&lt;/code&gt; and plan name to &lt;code&gt;updateUserMetadata&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const updateUserPlan = async (userId: string, planName: string) =&amp;gt; {
  await auth.updateUserMetadata(userId, {
    properties: {
      plan_name: planName,
    },
  });
};

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

&lt;/div&gt;



&lt;p&gt;All set! Our “Stripe” webhook is complete. If this was a real webhook, the last step is to secure the endpoint by verifying Stripe's signature. You can read more about that in &lt;a href="https://docs.stripe.com/webhooks?lang=node&amp;amp;ref=propelauth.com#verify-official-libraries" rel="noopener noreferrer"&gt;the official Stripe documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the last step of this tutorial, we’ll implement the image creation endpoint, which is responsible for validating the user’s API key and allowing requests only if the user is within seven days of their free trial or on the paid plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the Image Creation Endpoint
&lt;/h3&gt;

&lt;p&gt;This endpoint validates the user’s API key and checks if the user is still in the free trial period or has upgraded to a paid plan. Begin by creating a function to validate the user’s API key.&lt;/p&gt;

&lt;p&gt;If the key is invalid, the auth library will throw an error. We’ll catch it and return an “Unauthorized” error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const validateApiKey = async (apiKey: string, res: Response) =&amp;gt; {
  try {
    return (await auth.validatePersonalApiKey(apiKey)).user;
  } catch (error) {
    res.status(401).json({ error: "Invalid API key" });
    throw new Error("Invalid API key");
  }
};

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

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Tip: PropelAuth has a &lt;a href="https://docs.propelauth.com/reference/api/getting-started?ref=propelauth.com#download-the-postman-collection" rel="noopener noreferrer"&gt;Postman collection&lt;/a&gt; you can download, making it easy to view and test all of PropelAuth’s backend APIs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, we need to validate whether the GenAI image creation request from the user is valid. User requests are valid if they are on the free plan and it has been seven or fewer days since they signed up for an account or, of course if they are on the paid plan.&lt;/p&gt;

&lt;p&gt;First, inspect the &lt;code&gt;createdAt&lt;/code&gt; timestamp we get back from PropelAuth’s user object and use it to create a trial expiration date seven days from when the user’s account was created. If the user is on the Free plan and today’s date is past their trial expiration date, throw a Forbidden error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Allow the GenAI request if:
- The user is on Free plan and created date isn't past 7 days OR
- The user is on Paid plan */
const validateUserIsPayingOrOnTrial = async (validatedUser: UserMetadata, res: Response) =&amp;gt; {
  const trialExpirationDate = new Date(validatedUser.createdAt * 1000);
  trialExpirationDate.setDate(trialExpirationDate.getDate() + 7);
  const todaysDate = new Date();

  const planName = validatedUser.properties?.planName ?? "Free";
  if (planName === "Free" &amp;amp;&amp;amp; todaysDate.getTime() &amp;gt; trialExpirationDate.getTime()) {
    res.status(403).json({ error: "Trial expired. Please upgrade to Paid plan." });
    throw new Error("Trial expired. Please upgrade to Paid plan.");
  }
};

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

&lt;/div&gt;



&lt;p&gt;Finally, we combine the API key validation and GenAI request methods into the Image Create endpoint. Extract the API Key from the request body and pass it to &lt;code&gt;validateApiKey&lt;/code&gt;. If the key is valid, check that the user is permitted to make an image request. Creating a GenAI image is outside the scope of this tutorial, so instead, return a cute puppy picture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.post("/api/image/create", jsonParser, async (req: Request, res: Response) =&amp;gt; {
  const apiKey = req.body.apiKey;
  const validatedUser = await validateApiKey(apiKey, res);
  await validateUserIsPayingOrOnTrial(validatedUser, res);

  // User is permitted to create an image
  // Image creation outside the scope of this example
  // Instead, return a cute puppy picture
  res.json({ imageCreated: "https://picsum.photos/id/237/200/300" });
});

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

&lt;/div&gt;



&lt;p&gt;Here’s the complete code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.post("/api/image/create", jsonParser, async (req: Request, res: Response) =&amp;gt; {
  const apiKey = req.body.apiKey;
  const validatedUser = await validateApiKey(apiKey, res);
  await validateUserIsPayingOrOnTrial(validatedUser, res);

  // User is permitted to create an image
  // Image creation outside the scope of this example
  // Instead, return a cute puppy picture
  res.json({ imageCreated: "https://picsum.photos/id/237/200/300" });
});

/* Allow the GenAI request if:
- The user is on Free plan and created date isn't past 7 days OR
- The user is on Paid plan */
const validateUserIsPayingOrOnTrial = async (validatedUser: UserMetadata, res: Response) =&amp;gt; {
  const trialExpirationDate = new Date(validatedUser.createdAt * 1000);
  trialExpirationDate.setDate(trialExpirationDate.getDate() + 7);
  const todaysDate = new Date();

  const planName = validatedUser.properties?.planName ?? "Free";
  if (planName === "Free" &amp;amp;&amp;amp; todaysDate.getTime() &amp;gt; trialExpirationDate.getTime()) {
    res.status(403).json({ error: "Trial expired. Please upgrade to Paid plan." });
    throw new Error("Trial expired. Please upgrade to Paid plan.");
  }
};

const validateApiKey = async (apiKey: string, res: Response) =&amp;gt; {
  try {
    return (await auth.validatePersonalApiKey(apiKey)).user;
  } catch (error) {
    res.status(401).json({ error: "Invalid API key" });
    throw new Error("Invalid API key");
  }
};

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test the Complete Workflow
&lt;/h2&gt;

&lt;p&gt;We are code complete and can test the API we’ve created. We’ll need a personal user API key, so head back into the PropelAuth dashboard and navigate to the Account page. The easiest way is to select Preview in the upper right corner, then “Account.” This will launch your server’s Account page at a URL similar to &lt;a href="https://123.propelauthtest.com/en/login?ref=propelauth.com" rel="noopener noreferrer"&gt;&lt;code&gt;https://123.propelauthtest.com/en/login&lt;/code&gt;&lt;/a&gt;. Log in as your test user.&lt;/p&gt;

&lt;p&gt;Navigate to the Personal API Keys page and click “New API key.” For key expiration, choose any value you’d like. Like the server API key, copy it immediately, as it’s only shown once.&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%2Fuh6a0skkd6t2uk3pdhuu.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%2Fuh6a0skkd6t2uk3pdhuu.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When calling the webhook, we need to send the user’s ID along with the API key. Stripe would send it automatically in production, so this is only needed for testing. In the PropelAuth dashboard, navigate to the Users page. Find the user in the Users table, click the three vertical dots on the right-hand side, and choose “Copy User ID.”&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%2Fyk77a26wmi8l731lou20.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%2Fyk77a26wmi8l731lou20.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we’re ready to simulate what Stripe would do by testing the webhook endpoint! Start the app with &lt;code&gt;npm run dev&lt;/code&gt;, then make a POST request to &lt;code&gt;localhost:3000/webhook/stripe&lt;/code&gt; with the “successful payment” event in the body and the userId you copied from the dashboard. This will upgrade the user to the Paid plan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "type": "customer.subscription.created",
    "data": {
        "object": {
            "metadata": {
                "userId": "4ed07fa0-8366..."
            }
        }
    }
}

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

&lt;/div&gt;



&lt;p&gt;If it worked, you should see a &lt;code&gt;200 OK&lt;/code&gt; along with the response body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "received": true
}

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

&lt;/div&gt;



&lt;p&gt;Since we configured the Plan Name custom user property to be visible on the Account page, let’s verify that it was updated correctly. Back in the PropelAuth dashboard, choose the Preview button, then Account in the upper right-hand corner. Sign in to be redirected to the Settings page. Sure enough, under the Account Settings section, we can see that “Plan Name” has been set to “Paid!”&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%2Fcdvga33g188mzurqt03c.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%2Fcdvga33g188mzurqt03c.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s try the image creation endpoint. Make a POST request to &lt;a href="http://localhost:3000/api/image/create?ref=propelauth.com" rel="noopener noreferrer"&gt;&lt;code&gt;localhost:3000/api/image/create&lt;/code&gt;&lt;/a&gt; and in the request body, include your API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "apiKey": "9d14ac0d..."
}

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

&lt;/div&gt;



&lt;p&gt;Since our user is now a paying customer, the request should be successful, and the (hardcoded) created image should be returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "imageCreated": "https://picsum.photos/id/237/200/300"
}

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

&lt;/div&gt;



&lt;p&gt;Our reward for successfully testing our API? An adorable puppy.&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%2Fjlcgaspqsrcot5bkh196.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%2Fjlcgaspqsrcot5bkh196.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="200" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To recap what we’ve accomplished:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up PropelAuth for user and API key management&lt;/li&gt;
&lt;li&gt;Created an Express.js app with two API endpoints for a GenAI image creation service&lt;/li&gt;
&lt;li&gt;Created a test user and verified the API was correct&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But wait, there’s more!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Tracking API Key Signups
&lt;/h2&gt;

&lt;p&gt;PropelAuth goes beyond providing auth tools. It also surfaces useful information about your customers and their users, including audit logs and exploration of company/user data. In our case, we can track how many users have signed up for an API key, which is useful for reviewing adoption over time.&lt;/p&gt;

&lt;p&gt;Head into the PropelAuth dashboard and navigate to the Data page. Select User Audit Logs, and in the filter, choose “Created Personal API Key.” A list of all users that created a personal API key is displayed:&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%2Fkm1vnw4zjzilqgx5gmxt.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%2Fkm1vnw4zjzilqgx5gmxt.png" alt="Securing a GenAI Service with API Key Validation and Trial Management" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we created an image creation service for a GenAI company. We leveraged PropelAuth’s &lt;a href="https://docs.propelauth.com/overview/authentication/api-keys?ref=propelauth.com" rel="noopener noreferrer"&gt;API Key Authentication&lt;/a&gt;, &lt;a href="https://docs.propelauth.com/overview/user-management/user-properties?ref=propelauth.com#custom-user-properties" rel="noopener noreferrer"&gt;custom user properties&lt;/a&gt;, and &lt;a href="https://docs.propelauth.com/getting-started/basics/hosted-pages?ref=propelauth.com#user-account-page" rel="noopener noreferrer"&gt;account management&lt;/a&gt; features for the backend and API Key management. We also created an Express.js API with endpoints to simulate payment processing and image creation. With minimal configuration and code, we’ve implemented a common API authentication use case.&lt;/p&gt;

&lt;p&gt;Feel free to reference the &lt;a href="https://github.com/PropelAuth/demo-genai-api-keys?ref=propelauth.com" rel="noopener noreferrer"&gt;complete reference code&lt;/a&gt; while you build your own APIs.&lt;/p&gt;

</description>
      <category>apikeyauthentication</category>
      <category>express</category>
    </item>
    <item>
      <title>Protecting a Multi-tenant Next.js client/server app with PropelAuth</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Wed, 06 Nov 2024 21:28:49 +0000</pubDate>
      <link>https://dev.to/propelauth/protecting-a-multi-tenant-nextjs-clientserver-app-with-propelauth-4a76</link>
      <guid>https://dev.to/propelauth/protecting-a-multi-tenant-nextjs-clientserver-app-with-propelauth-4a76</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%2Ftwsk1tqtyvsa561k9b10.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%2Ftwsk1tqtyvsa561k9b10.png" alt="Protecting a Multi-tenant Next.js client/server app with PropelAuth" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll show you how to create a multi-tenant (or B2B) Next.js 14 app that creates digital coupons on demand using &lt;a href="https://openai.com/index/openai-api/?ref=propelauth.com" rel="noopener noreferrer"&gt;OpenAI's API&lt;/a&gt;. OpenAI will create a coupon image using a discount percentage entered by the user along with their assigned grocery store name. We'll use &lt;a href="https://www.propelauth.com/?ref=propelauth.com" rel="noopener noreferrer"&gt;PropelAuth&lt;/a&gt;, a B2B/multi-tenant authentication provider, to ensure only authorized users can create coupons. Out-of-the-box, it provides account sign up, login, logout functionality and we’ll use their roles and permissions system to verify the user is in the correct role before allowing them to create a coupon.&lt;/p&gt;

&lt;p&gt;You can find the complete code &lt;a href="https://github.com/PropelAuth/demo-b2b-coupon-generator?ref=propelauth.com" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;. Here's what the complete app looks like:&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%2Fxylbn15tgedhvacxd0ig.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%2Fxylbn15tgedhvacxd0ig.png" alt="Protecting a Multi-tenant Next.js client/server app with PropelAuth" width="800" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For background, imagine a grocery store corporation. They often own multiple chains consisting of different store brands. Suppose the marketing team wants to create on-demand digital coupons for a particular chain. They want to offer an internal company-wide solution, but given a coupon's monetary savings and potential for abuse, they'll need to ensure that only authorized team members can create coupons.&lt;/p&gt;

&lt;p&gt;The app we’ll create consists of a Next.js frontend used for generating coupons and a Next.js backend API used for communicating with OpenAI. Let's start by creating the coupon generation app.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Create the Digital Coupon App&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For this app, we'll use Next.js 14. Create a new project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@14

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

&lt;/div&gt;



&lt;p&gt;Feel free to select the options that best suit you. I selected TypeScript, App Router, and a &lt;code&gt;src&lt;/code&gt; directory for this tutorial's companion app, but not Tailwind. With the app created, build and start development mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
npm run dev

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

&lt;/div&gt;



&lt;p&gt;The app can now be viewed at &lt;code&gt;localhost:3000&lt;/code&gt;. Open up &lt;code&gt;src/app/page.tsx&lt;/code&gt; next, where we'll put the main functionality. Begin with the coupon generation markup consisting of a simple form that prompts for the grocery store name and the discount percentage to use in the generation of a new coupon image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [discount, setDiscount] = useState(0);
const [couponImage, setCouponImage] = useState(null);
const [isImageGenerating, setIsImageGenerating] = useState(false);

return &amp;lt;&amp;gt;
    &amp;lt;h2&amp;gt;Digital Coupon Generation&amp;lt;/h2&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;form onSubmit={createNewCoupon}&amp;gt;
            &amp;lt;p&amp;gt;
                &amp;lt;label htmlFor="store"&amp;gt;Grocery Store: &amp;lt;/label&amp;gt;
                &amp;lt;input type="text" id="store" /&amp;gt;
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                &amp;lt;label htmlFor="discount"&amp;gt;Discount (%): &amp;lt;/label&amp;gt;
                &amp;lt;input 
                    type="text" value={discount} id="discount"
                    onChange={(e) =&amp;gt; setDiscount(Number(e.target.value))}  
                /&amp;gt;
            &amp;lt;/p&amp;gt;
            &amp;lt;button type="submit"&amp;gt;Generate Coupon&amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
        {couponImage &amp;amp;&amp;amp; &amp;lt;img src={couponImage} style={{ height: '400px' }}/&amp;gt;}
        {isImageGenerating &amp;amp;&amp;amp; &amp;lt;p&amp;gt;Generating coupon image...&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
&amp;lt;/&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Next, add a &lt;code&gt;createNewCoupon&lt;/code&gt; function that calls our Next.js backend API (which will call OpenAI's API soon). Pass the grocery store name and the discount to the API in the request body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const createNewCoupon = async (event: React.FormEvent) =&amp;gt; {
  event.preventDefault();
  try {
      setIsImageGenerating(true);
      setCouponImage(null);
      const response =
          await fetch(`/api/coupons`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    discount: discount
                })
            })
      const data = await response.json()
      setCouponImage(data.image);
  } finally {
      setIsImageGenerating(false);
  }
}

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

&lt;/div&gt;



&lt;p&gt;With the frontend code in place, let's implement the backend API within our Next.js app using &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers?ref=propelauth.com" rel="noopener noreferrer"&gt;Route Handlers&lt;/a&gt; for simplicity.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Generate Coupon Images with OpenAI's API&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First, install the OpenAI SDK for Node.js:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Under the &lt;code&gt;src/app&lt;/code&gt; folder, create two nested folders: &lt;code&gt;api/coupons&lt;/code&gt;. This is our Next.js API endpoint. We'll execute calls to OpenAI server-side to protect our OpenAI API key. The API operates on a "pay as you go" usage model. As of the time of writing, you must add a minimum of $5 to your account before using it, so we definitely don't want the key to fall into the wrong hands!&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;route.ts&lt;/code&gt; under the &lt;code&gt;coupons&lt;/code&gt; folder. Import OpenAI so we can use their image generation capabilities, then extract the discount percentage from the request body. Set grocery store to a string placeholder for now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/app/api/coupons/route.ts
import { NextResponse } from 'next/server'
import OpenAI from 'openai';

export async function POST(request: Request) {
  const body = await request.json();
  const discount = body.discount;
  const groceryStore = "orgName";
}

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

&lt;/div&gt;



&lt;p&gt;Next, call the OpenAI image generation service, keeping the prompt simple: &lt;code&gt;a coupon for ${discount}% off any purchase at ${groceryStore}, with code ${couponCode}&lt;/code&gt;. Choose the DALL·E 3 model since the image quality is better, and select a size that mimics a classic horizontally printed coupon. Finally, extract the URL of the generated image and send it back in the API response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const openai = new OpenAI();
const couponCode = generateCouponCode()

const image = await openai.images.generate({
    model: 'dall-e-3',
    size: "1792x1024",
    prompt: `a coupon for ${discount}% off any purchase at ${groceryStore}, with code ${couponCode}`
});

saveCouponCodeToDb(groceryStore, couponCode)
const imageUrl = image.data[0].url;
return NextResponse.json({ image: imageUrl })

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

&lt;/div&gt;



&lt;p&gt;The complete code looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NextResponse } from 'next/server'
import OpenAI from 'openai';

export async function POST(request: Request) {
    const body = await request.json();
    const discount = body.discount;
    const groceryStore = "orgName";

    const openai = new OpenAI();
    const couponCode = generateCouponCode()
    const image = await openai.images.generate({
        model: 'dall-e-3',
        size: "1792x1024",
        prompt: 
        `a coupon for ${discount}% off any purchase at ${groceryStore}, with code ${couponCode}`
    });

    saveCouponCodeToDb(groceryStore, couponCode)
    const imageUrl = image.data[0].url;
    return NextResponse.json({ image: imageUrl })
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Store the OpenAI Key in an Environment File&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The last step in creating the coupon generation app is placing our OpenAI API key into an environment file. Create a &lt;code&gt;.env&lt;/code&gt; file at the project's root, then add &lt;code&gt;OPENAI_API_KEY=&lt;/code&gt; along with the API key value. &lt;strong&gt;Note:&lt;/strong&gt; Be careful committing this file to source control! In a public repository, the key will be leaked.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Generate a Coupon&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We have a working solution now! The app sends an API request to our backend, generates an image using OpenAI, and displays it to the end user. Here it is in action:&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%2Fe05r9wuo7ggn86gllb6x.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%2Fe05r9wuo7ggn86gllb6x.png" alt="Protecting a Multi-tenant Next.js client/server app with PropelAuth" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's just one (major) problem: anyone at the company (or the public) can use the app!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Securing the App with PropelAuth&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To secure the app quickly, we'll integrate PropelAuth, a B2B user authentication platform with easy integration and straightforward APIs for developers. Head on over to &lt;a href="http://propelauth.com/?ref=propelauth.com" rel="noopener noreferrer"&gt;propelauth.com&lt;/a&gt; and create a free account. Afterward, navigate to the &lt;a href="https://docs.propelauth.com/getting-started/quickstart-fe?ref=propelauth.com" rel="noopener noreferrer"&gt;Frontend Quickstart&lt;/a&gt; in their docs. Choose "Next.js App Router" as the frontend and backend framework and follow the setup guide. It'll walk you through creating a project, setting up signup, login, and account pages, and installing and configuring PropelAuth in our Next.js app.&lt;/p&gt;

&lt;p&gt;Go ahead, I'll wait! Come back here after completing the middleware setup step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Before continuing, ensure you have created &lt;a href="https://docs.propelauth.com/getting-started/basics/organizations?ref=propelauth.com#creating-orgs" rel="noopener noreferrer"&gt;an Organization&lt;/a&gt; within the PropelAuth dashboard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the PropelAuth dashboard, navigate to &lt;code&gt;Organization Settings&lt;/code&gt; then under Membership Settings toggle on “User must be in at least one org” and set max number of orgs per user to “1.”&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%2F7g2vtrbc205vwpg7a6ta.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%2F7g2vtrbc205vwpg7a6ta.png" alt="Protecting a Multi-tenant Next.js client/server app with PropelAuth" width="800" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will make sure that users must be in an organization before they are able to use your product.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Limiting Public Access with Basic Authentication&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;One of the great things about PropelAuth is not only how much time it saves you but also the security peace of mind it provides. To make this app feel more "complete" as well as limit public access, let's use their hosted sign-up and login pages as the first step in protecting our application.&lt;/p&gt;

&lt;p&gt;Head back to &lt;code&gt;page.tsx&lt;/code&gt; and import PropelAuth's library. Use the provided React hooks to retrieve user information and access login/logout functionality. We'll also retrieve the signed-in user's organization to which they belong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/app/page.tsx
import {useUser, useRedirectFunctions, useLogoutFunction} from "@propelauth/nextjs/client";

export default function Home() {
    const {loading, user} = useUser()
    const {
        redirectToSignupPage, redirectToLoginPage, redirectToAccountPage
    } = useRedirectFunctions()
    const logoutFn = useLogoutFunction()
    const org = user?.getActiveOrg()

    // ... snip
}

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

&lt;/div&gt;



&lt;p&gt;You’ll notice the use of &lt;code&gt;getActiveOrg()&lt;/code&gt; - this is to get the user’s active organization. Over in &lt;code&gt;api/auth/[slug]/route.ts&lt;/code&gt;, implement the &lt;code&gt;getDefaultActiveOrgId&lt;/code&gt; function that executes automatically for us after the user logs in. You have full control over what the active org is. Since earlier in this tutorial we configured the Organization Settings to require a user to be in one org (and only one), we can simply return that one org:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// api/auth/[slug]/route.ts
const routeHandlers = getRouteHandlers({
  postLoginRedirectPathFn: (req: NextRequest) =&amp;gt; {
    return "/"
  },
  getDefaultActiveOrgId: (req: NextRequest, user: UserFromToken) =&amp;gt; {
    return user.getOrgs()[0].orgId;
  },
})

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

&lt;/div&gt;



&lt;p&gt;Next, update the code to show a loading message when the authentication process is taking place. If the &lt;code&gt;user&lt;/code&gt; object exists (aka an employee is signed-in), display the user's email and organization they are a part of. Finally, display Account and Logout buttons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
  &amp;lt;div className={styles.page}&amp;gt;
    &amp;lt;main className={styles.main}&amp;gt;
      { loading ? &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt; : null }
      { user ?
        &amp;lt;div&amp;gt;
          &amp;lt;p&amp;gt;You are logged in as {user.email}&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;You are in organization: {org!.orgName}&amp;lt;/p&amp;gt;

          &amp;lt;button onClick={() =&amp;gt; redirectToAccountPage()}&amp;gt;Account&amp;lt;/button&amp;gt;
          &amp;lt;button onClick={logoutFn}&amp;gt;Logout&amp;lt;/button&amp;gt;

// snip

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

&lt;/div&gt;



&lt;p&gt;Further down, in the else statement signifying the user hasn't signed in yet, explain that they need to sign in to use the app and provide the appropriate Login and Signup buttons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:
&amp;lt;div&amp;gt;
  &amp;lt;p&amp;gt;You are not logged in&amp;lt;/p&amp;gt;
  &amp;lt;button onClick={() =&amp;gt; redirectToLoginPage()}&amp;gt;Login&amp;lt;/button&amp;gt;
  &amp;lt;button onClick={() =&amp;gt; redirectToSignupPage()}&amp;gt;Signup&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Thanks to PropelAuth, we now have a working signup and login workflow with minimal custom code required. Log into your PropelAuth account now to see the user's info displayed:&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%2Ff6yzxwidzn6ksiqswklk.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%2Ff6yzxwidzn6ksiqswklk.png" alt="Protecting a Multi-tenant Next.js client/server app with PropelAuth" width="706" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The coupon generation functionality has been placed behind a login screen but is available to all employees and the API is publicly accessible as well. Let's change that.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Limiting Employee Access with PropelAuth Roles&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;PropelAuth offers multiple ways to secure an app via &lt;a href="https://docs.propelauth.com/getting-started/orgs-roles-and-permissions?ref=propelauth.com" rel="noopener noreferrer"&gt;orgs, roles, and permissions&lt;/a&gt;. In this example, let's use a role to define what users within each organization (grocery store) can do.&lt;/p&gt;

&lt;p&gt;By default, PropelAuth gives us three roles: Owner, Admin, and Member. In this case, only Owners and Admins (aka grocery store managers) should be able to create new coupons. Once again, PropelAuth makes this easy. Use the &lt;code&gt;isAtLeastRole&lt;/code&gt; function to state that users must at least be an Admin or Owner to access coupon generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ user ?       
  // snip...
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;Digital Coupon Generation&amp;lt;/h2&amp;gt;

    { org?.isAtLeastRole("Admin") ?
      // snip: coupon code generation
      : &amp;lt;p&amp;gt;You must be a Store Manager or Owner to create a coupon.&amp;lt;/p&amp;gt; 
    }
  &amp;lt;/div&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;One last detail before we're done! Our image generation API needs to know the employee's grocery store name in order to insert it into the coupon image. Employees should only be able to generate coupons for stores where they are employed. In the store text field, automatically fill in the organization name and disable editing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// page.tsx
&amp;lt;label htmlFor="store"&amp;gt;Grocery Store: &amp;lt;/label&amp;gt;
&amp;lt;input type="text" placeholder={org?.orgName} disabled={true} id="store" /&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Here's how the app looks for a "Member" employee now:&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%2Fngagcjr7z3l1zthf4rqq.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%2Fngagcjr7z3l1zthf4rqq.png" alt="Protecting a Multi-tenant Next.js client/server app with PropelAuth" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Securing the Coupons API Endpoint
&lt;/h3&gt;

&lt;p&gt;We’ve protected the frontend but still need to protect the coupons API endpoint. First, add a call to &lt;code&gt;getUser()&lt;/code&gt; at the beginning of the request. If the user object is undefined, that means authentication has failed and we can reject the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function POST(request: Request) {
  const user = await getUser();
  if (!user) {
    return NextResponse.json({ 
        message: "You must be logged in to generate a coupon." 
      }, 
      { status: 401 }
    );
  }

// ... snip ...

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

&lt;/div&gt;



&lt;p&gt;Next, we want to ensure the user is a store manager or owner, just like we implemented on the frontend. Access the user’s active org, then check that their role is at least Admin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const activeOrg = user.getActiveOrg();
if (!activeOrg || !activeOrg.isAtLeastRole("Admin")) { 
  return NextResponse.json({ 
      message: "You need to be a Store Manager or Owner to create a coupon." 
    }, 
    { status: 403 }
  );
}

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

&lt;/div&gt;



&lt;p&gt;As a final step, change the grocery store from a placeholder string to the active organization name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const groceryStore = activeOrg.orgName;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congrats! We've created a complete Next.js 14 frontend and backend API that safeguards access to coupon generation using PropelAuth's end-to-end user authentication services.&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%2Fznajlsw1g7js9ff3dyvx.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%2Fznajlsw1g7js9ff3dyvx.png" alt="Protecting a Multi-tenant Next.js client/server app with PropelAuth" width="800" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What's Next?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are several paths to take with this grocery store app. We could add &lt;a href="https://docs.propelauth.com/overview/authentication/login-methods?ref=propelauth.com" rel="noopener noreferrer"&gt;additional login methods&lt;/a&gt; in just a few button clicks, go deeper with authorization using &lt;a href="https://docs.propelauth.com/overview/authorization/managing-roles-permissions?ref=propelauth.com#roles-vs-permissions" rel="noopener noreferrer"&gt;permissions&lt;/a&gt;, and more. Happy building (and securing)!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>b2b</category>
      <category>openai</category>
    </item>
    <item>
      <title>Ionic Vue: The UI library for Vue 3</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Fri, 20 Nov 2020 17:04:11 +0000</pubDate>
      <link>https://dev.to/ionic/ionic-vue-the-ui-library-for-vue-3-7m5</link>
      <guid>https://dev.to/ionic/ionic-vue-the-ui-library-for-vue-3-7m5</guid>
      <description>&lt;p&gt;The Vue team &lt;a href="https://news.vuejs.org/issues/186" rel="noopener noreferrer"&gt;released Vue 3 in September&lt;/a&gt; to much-deserved fanfare. With improved performance, smaller bundle sizes, new APIs, and a revamped foundation to support future framework iterations, it’s no wonder the Vue community is excited.&lt;/p&gt;

&lt;p&gt;Unfortunately, many UI libraries aren’t compatible with Vue 3 yet. If you’re looking for one that is production-ready right now, then check out &lt;a href="https://ionicframework.com/docs/vue/quickstart" rel="noopener noreferrer"&gt;Ionic Vue&lt;/a&gt;, a UI library with over 100 mobile and desktop components built for Vue 3. Let’s take a look at everything it has to offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ionic Vue: A Native Vue version of Ionic Framework
&lt;/h2&gt;

&lt;p&gt;Ionic Framework is an open-source UI toolkit focused on building high-quality apps for native iOS, native Android, and the web! It’s built from the ground up with HTML, CSS, and JavaScript, so web developers should feel right at home. The components allow developers to build native experiences, all while using web technology. Now used by millions of developers, Ionic &lt;a href="https://appfigures.com/top-sdks/development" rel="noopener noreferrer"&gt;powers &amp;gt; 15% of all&lt;/a&gt; app store apps.&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%2Fi%2Fynoy3eko4qzkmme0h3u2.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%2Fi%2Fynoy3eko4qzkmme0h3u2.png" alt="Ionic Vue music app example" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ionic Vue is the native Vue version of Ionic Framework. It acts as a wrapper for the core UI component library (aptly named &lt;code&gt;@ionic/core&lt;/code&gt;), allowing Ionic to support all Vue 3 features with ease. Here’s the entry point of an Ionic Vue app:&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="c1"&gt;// Vue 3 component definition&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;IonApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IonRouterOutlet&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;@ionic/vue&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;defineComponent&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;vue&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="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineComponent&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;App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;IonApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;IonRouterOutlet&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Template with Ionic Framework UI components --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ion-app&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ion-router-outlet&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ion-app&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it’s written in modern web code. Ionic likes to say that you don’t &lt;em&gt;learn&lt;/em&gt; Ionic, per se - you leverage your existing web development skills to build apps using their UI components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Ready for Vue 3
&lt;/h2&gt;

&lt;p&gt;The Ionic team released Ionic Vue shortly after the launch of Vue 3. Other libraries are still implementing Vue 3 support, so how was Ionic able to ship so fast? The answer: All Ionic Framework UI components are web components consumed by web developers using official framework bindings (including Angular, React, Vue, and basically any other JavaScript framework on the market today, or tomorrow). Developers using each framework get the experience they are familiar with, such as the framework’s routing system, lifecycle events, official APIs and tooling, and more. Frankly, it’s a win-win! Learn more about how Ionic accomplished their “Ionic for Everyone” milestone &lt;a href="https://ionicframework.com/blog/introducing-ionic-4-ionic-for-everyone/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s not all. Ever wish you could use your favorite UI library on multiple platforms but couldn’t because they didn’t leverage each platform’s specific design language? You’re in luck. Every Ionic component &lt;a href="https://ionicframework.com/docs/core-concepts/fundamentals#adaptive-styling" rel="noopener noreferrer"&gt;adapts its look&lt;/a&gt; to the platform the app is running on, such as Cupertino on iOS and Material Design on Android. Through these subtle design changes between platforms, developers achieve a native look and feel while users are happy to receive a high-quality app experience. This adaptation is applied automatically and is entirely configurable should you wish to make theming changes to fit your brand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Creating a Vue 3-powered Ionic app is a breeze. Begin by installing the Ionic CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @ionic/cli@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create an Ionic Vue application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ionic start my-vue-app &lt;span class="nt"&gt;--type&lt;/span&gt; vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After answering a few questions, you can start a local development server with &lt;code&gt;ionic serve&lt;/code&gt;. This command uses the Vue CLI to compile your app, start a dev server, and open your app in a new browser window.&lt;/p&gt;

&lt;p&gt;From here, you can explore Ionic’s &lt;a href="https://ionicframework.com/docs/components" rel="noopener noreferrer"&gt;100+ UI components&lt;/a&gt; or take the &lt;a href="https://ionicframework.com/docs/vue/your-first-app" rel="noopener noreferrer"&gt;“First App” tutorial&lt;/a&gt; that covers Ionic’s core app development concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Ionic Vue to an Existing Vue 3 App
&lt;/h2&gt;

&lt;p&gt;If you’ve already started working on a Vue 3 app, you can add in Ionic Framework components. First, install two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @ionic/vue @ionic/vue-router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, add the &lt;code&gt;IonicVue&lt;/code&gt; package into your app.&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="c1"&gt;// main.js&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;IonicVue&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;@ionic/vue&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="nx"&gt;App&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;./App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;router&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;./router&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;IonicVue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, there are some small &lt;a href="https://ionicframework.com/docs/intro/cdn#routing" rel="noopener noreferrer"&gt;routing and CSS changes&lt;/a&gt; to make. Once those steps are complete, you can start &lt;a href="https://ionicframework.com/docs/components" rel="noopener noreferrer"&gt;adding Ionic’s components&lt;/a&gt; to your Vue 3 app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Deploy your Vue 3 app to mobile
&lt;/h2&gt;

&lt;p&gt;You’ve built a great Vue 3 app that works well on the web and desktop. What about native mobile? Once again, Ionic has you covered. With &lt;a href="https://capacitorjs.com/" rel="noopener noreferrer"&gt;Capacitor&lt;/a&gt;, Ionic’s official cross-platform native runtime, you can deploy your Vue 3 apps as progressive web apps &lt;em&gt;and&lt;/em&gt; iOS/Android apps, all from the same codebase.&lt;/p&gt;

&lt;p&gt;Capacitor also provides APIs with functionality covering all three platforms. Here’s the Geolocation API, for example:&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="c1"&gt;// Capacitor Geolocation plugin example&lt;/span&gt;
&lt;span class="nf"&gt;setup&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;locationData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&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;getLocation&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Geolocation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Plugins&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;results&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;Geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentPosition&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;locationData&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="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;long&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longitude&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locationData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getLocation&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ion-button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"getLocation()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Where am I?&lt;span class="nt"&gt;&amp;lt;/ion-button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ locationData }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that there’s no separate logic for each platform (“web,” “iOS,” or “Android”) in the code. That’s because Capacitor automatically detects the platform the app is running on then calls the appropriate native layer code. With Core APIs like these, coupled with full access to native SDKs and a growing roster of &lt;a href="https://capacitorjs.com/community" rel="noopener noreferrer"&gt;community-supported plugins&lt;/a&gt;, Capacitor empowers you to build truly native mobile apps from your Vue 3 projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Building awesome Vue 3 apps for web and mobile
&lt;/h2&gt;

&lt;p&gt;If you’ve been holding off from trying Vue 3 until more libraries become compatible, now’s a great time to give Ionic Vue a try. With Ionic, you can build a fully-featured app then deploy it to multiple platforms with Capacitor.&lt;/p&gt;

&lt;p&gt;For a more in-depth look at Ionic Vue, we recommend checking our &lt;a href="https://ionicframework.com/docs/vue/quickstart" rel="noopener noreferrer"&gt;Quickstart Guide&lt;/a&gt;. For a more hands-on experience, take a look at our &lt;a href="https://ionicframework.com/docs/vue/your-first-app" rel="noopener noreferrer"&gt;Build Your First App Guide&lt;/a&gt;. If you already have a Vue 3 app, &lt;a href="https://capacitorjs.com/docs/getting-started#adding-capacitor-to-an-existing-web-app" rel="noopener noreferrer"&gt;bring it to iOS and Android with Capacitor&lt;/a&gt;. Built something neat? Share your Ionic Vue app with us over &lt;a href="https://twitter.com/IonicFramework" rel="noopener noreferrer"&gt;@IonicFramework&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>ionic</category>
    </item>
    <item>
      <title>Converting a base64 string to a blob in JavaScript</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Thu, 27 Aug 2020 14:28:13 +0000</pubDate>
      <link>https://dev.to/ionic/converting-a-base64-string-to-a-blob-in-javascript-35kl</link>
      <guid>https://dev.to/ionic/converting-a-base64-string-to-a-blob-in-javascript-35kl</guid>
      <description>&lt;p&gt;Web browsers provide a variety of data primitives that web developers use to manage, manipulate, and store data - from plain text, to files, images, videos and more. However, using them correctly and effectively can be confusing. One such example is converting a base64 string to a blob using JavaScript. A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" rel="noopener noreferrer"&gt;blob&lt;/a&gt; represents binary data in the form of files, such as images or video. Suppose you have an image in string format that needs to be uploaded to a server. However, the available API accepts the image in blob format only. What do you do?&lt;/p&gt;

&lt;p&gt;According to various solutions &lt;a href="https://stackoverflow.com/questions/27980612/converting-base64-to-blob-in-javascript" rel="noopener noreferrer"&gt;around the Internet&lt;/a&gt;, conversion appears to be complex. Manipulating byte arrays? No thanks. Fortunately, there's an easier, modern approach available thanks to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt;. It's a powerful feature built into all web browsers that is commonly used to fetch resources across the network, like making HTTP requests to your backend APIs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Fetch&lt;/code&gt; returns a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" rel="noopener noreferrer"&gt;Response object&lt;/a&gt;. As it turns out, it can convert data into more than just JSON, it can also return array buffers, form data, and blobs. In this case, we'll use blobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy as one, two
&lt;/h2&gt;

&lt;p&gt;First, pass a base64 string to &lt;code&gt;fetch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64Data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aGV5IHRoZXJl&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;base64&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;base64Data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the format of the base64 string, you might need to prepend content type data. For example, a JPEG image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64Response&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="s2"&gt;`data:image/jpeg;base64,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base64Data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, convert the response to a blob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&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;base64Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! From here, you can upload it to a server, display it on the screen, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Converting a blob to a base64 string
&lt;/h2&gt;

&lt;p&gt;What about reversing the conversion, from a blob to a base64 string? Unfortunately, this is a bit more complicated (though if you know of a better approach, let me know in the comments!).&lt;/p&gt;

&lt;p&gt;I encountered this real-world example recently while building a feature for the &lt;a href="https://github.com/ionic-team/ionifits" rel="noopener noreferrer"&gt;Ionifits demo app&lt;/a&gt;. While entering a company expense, users take a photo of the expense receipt. To implement this, I used the Capacitor &lt;a href="https://capacitorjs.com/docs/apis/camera" rel="noopener noreferrer"&gt;Camera&lt;/a&gt; and &lt;a href="https://capacitorjs.com/docs/apis/filesystem" rel="noopener noreferrer"&gt;Filesystem&lt;/a&gt; APIs.&lt;/p&gt;

&lt;p&gt;After taking a photo, the Camera API returns a blob URL, which looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://myapp.com/cc7622aa-271b-4ee3-b707-28cbc84bc37a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To save the photo permanently to the filesystem (blobs are objects temporarily loaded into browser memory), the Filesystem API requires the data to be in base64 format, so we must convert the blob into a base64 string.&lt;/p&gt;

&lt;p&gt;There are a variety of techniques to do so, but as a fan of asynchronous programming, I recommend creating a Promise then using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader" rel="noopener noreferrer"&gt;FileReader API&lt;/a&gt; to convert the blob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;convertBlobToBase64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&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="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&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;base64String&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;convertBlobToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilà! Using modern web development techniques, converting blobs and base64 strings back and forth isn’t so scary after all. What other tips or tricks have you picked up recently in your web development journey?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Special thanks to Capacitor community member &lt;a href="https://www.wyldweb.com/" rel="noopener noreferrer"&gt;Matt Wyld&lt;/a&gt; for his feedback, especially performance testing the Fetch approach on mobile. &lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>css</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Farewell, PhoneGap: Reflections on my Hybrid App Development Journey</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Thu, 13 Aug 2020 12:13:56 +0000</pubDate>
      <link>https://dev.to/ionic/farewell-phonegap-reflections-on-my-hybrid-app-development-journey-10dh</link>
      <guid>https://dev.to/ionic/farewell-phonegap-reflections-on-my-hybrid-app-development-journey-10dh</guid>
      <description>&lt;p&gt;Adobe has officially announced the shutdown of &lt;a href="https://blog.phonegap.com/update-for-customers-using-phonegap-and-phonegap-build-cc701c77502c" rel="noopener noreferrer"&gt;PhoneGap and PhoneGap Build&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the context of the hybrid app development world, this is the end of an era. It's certainly the case for me: it sped up my transition from .NET to web development, and ultimately led to me landing a wonderful role at Ionic. A heartfelt thanks to the team(s) at Adobe and those in the community who supported me along the way! &lt;/p&gt;

&lt;p&gt;PhoneGap has had such a positive impact on my career. Here's my hybrid app development journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  From .NET to Android Development
&lt;/h2&gt;

&lt;p&gt;It was 2010, and I had just bought my first smartphone - a clunky Android device. I was .NET developer building tools and WinForm desktop apps for a SaaS company. That was fun, but my work was only used by a handful of corporate clients. This Android phone had potential - I could build an "app" and reach anyone in the world via the app marketplaces? Sign me up!&lt;/p&gt;

&lt;p&gt;I learned Java in college, so Android development was the obvious choice to learn. I bought a beginner Android book, "Hello, Android", and got to work. The dev experience was brutal to say the least. Between confusing Eclipse IDE errors and trying to understand the ins and outs of mobile development, I almost gave up multiple times.&lt;/p&gt;

&lt;p&gt;I pushed through, and in February 2011 released the app. Frustrated that Netflix movies would expire and be removed from my instant queue without notice, I explored my options. I discovered that Netflix had an open API, and while not used on the site, every movie was assigned a "movie availability" (expiration) date! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FixMyQ&lt;/strong&gt; was born: it displayed each movie in your Instant Queue along with its expiration date. Optionally, with one button tap, you could rearrange your entire queue by the movies expiring next. In practice, after deciding to watch something on Netflix, you could pull up my app first then pick based on what was expiring soonest:&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%2Fi%2Fdtb91gsndvx8z7ok57pj.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%2Fi%2Fdtb91gsndvx8z7ok57pj.png" alt="FixMyQ - Queue" width="480" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite being super ugly (ha), the app worked quite well and was decently popular. &lt;/p&gt;

&lt;h2&gt;
  
  
  What About iOS?
&lt;/h2&gt;

&lt;p&gt;The app was doing well, but I was missing out on a huge audience: iOS users. I quickly realized that targeting iOS meant that I had to completely rewrite the app - yikes! Fortunately, there was another way: PhoneGap.&lt;/p&gt;

&lt;p&gt;Through my day job work and attending developer conferences, I noticed this thing called "JavaScript" was skyrocketing in popularity. I began to actively seek out opportunities at work to use it - landing on ASP.NET MVC, jQuery, and Knockout.js. I don't remember exactly how I found PhoneGap, but I loved the idea of "write once, run everywhere": targeting web, iOS, and Android with one code base.&lt;/p&gt;

&lt;p&gt;Additionally, their &lt;a href="https://phonegap.com/blog/2012/05/09/phonegap-beliefs-goals-and-philosophy/" rel="noopener noreferrer"&gt;beliefs, goals, and philosophy&lt;/a&gt; really struck a chord. The team recognized that the web &lt;strong&gt;was not&lt;/strong&gt; a first class development platform, but they fully believed it could be, laying out a strong vision for its future.&lt;/p&gt;

&lt;p&gt;What really stood out at the time (and still does) was this line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The ultimate purpose of PhoneGap is to cease to exist."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To this day, I've yet to see another project put that front and center! It made sense, though: they were committed to the "standardization of the web as a platform."&lt;/p&gt;

&lt;p&gt;I was convinced and started building FixMyQ for iOS using PhoneGap 1.2.0. Unfortunately, I didn't get very far: Netflix deprecated then eventually shut down their open API - effectively killing the app. It was a great first mobile app project though and made for a &lt;a href="https://www.netkow.com/fixmyq-android-app-post-mortem/" rel="noopener noreferrer"&gt;fun retrospective&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(Not so) hilariously, in 2020 Netflix still acts like movies don't expire. C'mon, Netflix!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hooked on Hybrid
&lt;/h2&gt;

&lt;p&gt;Despite shutting down my first app, I was excited by PhoneGap's potential and got to work right away on a new app idea. Work had just bought everyone a Fitbit device. I was also in the WeightWatchers program, so I wondered what it would take to integrate them together. A few months later, &lt;strong&gt;Fitwatchr&lt;/strong&gt; was born and thanks to PhoneGap, I created iOS, Android, and Windows Phone apps all from one code base:&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%2Fi%2F6szyrjomn2s4rvfpvw5t.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%2Fi%2F6szyrjomn2s4rvfpvw5t.png" alt="Fitwatchr - Activity" width="361" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Besides improving my web development skills, Fitwatchr was my first foray into becoming somewhat of an  entrepreneur: in order to improve app sales, I learned so much about marketing, sales, and product development, ultimately earning thousands of dollars over a ~5 year timespan. As the app started making waves, I partnered with my good friend &lt;a href="http://davidlapekas.com/" rel="noopener noreferrer"&gt;David Lapekas&lt;/a&gt; for design and marketing help - he was absolutely critical to my apps' success.&lt;/p&gt;

&lt;p&gt;You might say I was "hooked on hybrid!" &lt;/p&gt;

&lt;p&gt;My next app scratched another itch. I love craft beer and had gotten really into tracking beer tastings with Untappd (another PhoneGap/Cordova - and later, Ionic Framework - app!). Their app was great, but didn't work well in offline scenarios (such as beer festivals or inside crowded brewery tasting rooms) where cell service was weak or wifi non-existent. With &lt;strong&gt;BeerSwift&lt;/strong&gt;, you can queue up the beers you're drinking, rate them, then check them all into Untappd with one button tap (once you're back online):&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%2Fi%2Fvufmt2fn8kuwkb4gyba6.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%2Fi%2Fvufmt2fn8kuwkb4gyba6.png" alt="BeerSwift - Queue" width="527" height="937"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These apps were so much fun to build. I worked on them during the days of Angular 1, but was honestly scared off by how complex it seemed. So instead, I opted for a simpler stack: Vanilla HTML/CSS/JavaScript paired with jQuery, KendoUI Mobile for UI components, and Knockout.js for declarative bindings and automatic UI refresh.&lt;/p&gt;

&lt;p&gt;As you can tell from those screenshots, the apps look much better than my original Android app, but the UI still has some rough edges. Someday I'll rewrite them using &lt;a href="https://ionicframework.com/docs/components" rel="noopener noreferrer"&gt;Ionic Framework&lt;/a&gt; UI components...&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter PhoneGap Build
&lt;/h2&gt;

&lt;p&gt;While PhoneGap makes it easy to create an app that runs on all platforms, in practice managing each platform is challenging, especially as a solo developer. There are nuances to each one as well as headaches with security profiles and certificates (&lt;em&gt;cough cough&lt;/em&gt; iOS!). Enter Adobe's PhoneGap Build service, which let you build your PhoneGap-based Android, iOS, and Windows Phone apps in the cloud. It was incredibly successful as one of the early attempts at Mobile CI/CD since you could avoid wrestling with the challenges of native IDEs and tooling. Everyone in the PhoneGap community embraced it: solo devs, teams, and large companies.&lt;/p&gt;

&lt;p&gt;After gaining lots of experience with the service, I began sharing various &lt;a href="https://www.netkow.com/categories/phonegap/" rel="noopener noreferrer"&gt;tips and tricks&lt;/a&gt; on my personal blog. I'm particularly proud of the &lt;a href="https://www.netkow.com/cut-your-phonegap-build-app-size-in-half-with-this-one-weird-trick/" rel="noopener noreferrer"&gt;"Cut Your PhoneGap Build App Size In Half With This One Weird Trick!"&lt;/a&gt; post - one of my first attempts at "marketing." 😂&lt;/p&gt;

&lt;h2&gt;
  
  
  Conference Talk and PhoneGap Build Pluralsight Course
&lt;/h2&gt;

&lt;p&gt;It was rewarding to share what I'd learned with the community. I kept plugging away on both app development and blogging. From there, I decided to give public speaking a try, presenting a talk on hybrid app development at &lt;a href="https://old.thatconference.com/sessions/Session/1171" rel="noopener noreferrer"&gt;That Conference 2014&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By 2015, hybrid app development had become far less niche and I had built up a lot of experience with several successful apps under my belt. I looked for my next challenge and settled on creating a video course on PhoneGap Build. With only a small blog audience, I turned to Pluralsight. I was a long-time fan - they are known for their high quality courses and popular authors. After a brief audition, I was in! You can read about that &lt;em&gt;2 year journey&lt;/em&gt; (yeah) &lt;a href="https://www.netkow.com/pluralsight-course-retrospective/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It was incredibly challenging with lots of ups and downs, but in the end, I pulled it off.&lt;/p&gt;

&lt;blockquote&gt;   &lt;p&gt; &lt;a href="https://www.instagram.com/p/BWbqB8njmXD/" rel="noopener noreferrer"&gt;So unbelievably stoked! A long journey comes to an end. My @pluralsight course has been released! Cheers to a wonderful editorial team and @nursingaround 's support. #PhoneGap #Cordova #MobileDev&lt;/a&gt;&lt;/p&gt; &lt;p&gt;A post shared by &lt;a href="https://www.instagram.com/dotnetkow/" rel="noopener noreferrer"&gt; Matt Netkow&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/dotnetkow"&gt;@dotnetkow&lt;/a&gt;) on &lt;time&gt;Jul 11, 2017 at 8:53pm PDT&lt;/time&gt;&lt;/p&gt;
&lt;/blockquote&gt; 

&lt;p&gt;The Pluralsight course was not a major hit by any means, but it was definitely a personal success: I learned basic video editing and production, and improved my writing and speaking skills along the way - all skills I would eventually use regularly in my DevRel role at Ionic.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Hint of the Future
&lt;/h2&gt;

&lt;p&gt;At some point during the development of my PhoneGap apps, I became frustrated trying to create the variety of icons and splash screens. Besides the act of creating them (I'm certainly no designer!), generating them for each platform and dimension was tedious. I'm not entirely sure, but I believe this was the first time I learned about Ionic: I stumbled upon &lt;a href="https://ionicframework.com/blog/automating-icons-and-splash-screens/" rel="noopener noreferrer"&gt;a blog post of theirs&lt;/a&gt; on automating icon/splash screen generation.&lt;/p&gt;

&lt;p&gt;I created an Ionic account just to generate those images for free with the Ionic CLI (they were originally built in the cloud). Thanks, Ionic! 😬&lt;/p&gt;

&lt;p&gt;Little did I know where I'd end up someday...&lt;/p&gt;
&lt;h2&gt;
  
  
  Writing for the PhoneGap Blog
&lt;/h2&gt;

&lt;p&gt;As part of the efforts to promote my PhoneGap Build Pluralsight course, I reached out to the PhoneGap team and asked about writing a post for the official blog. They graciously accepted, no doubt largely due to my course and personal PhoneGap blog posts, so I wrote &lt;a href="https://blog.phonegap.com/hybrid-mobile-apps-are-overtaking-native-951a3aacacd1" rel="noopener noreferrer"&gt;"Hybrid Mobile Apps are Overtaking Native."&lt;/a&gt; This was a fun one: I covered the most popular concerns about hybrid app development from a &lt;em&gt;fresh 2017 perspective&lt;/em&gt;: performance, design, frameworks, and tooling. &lt;/p&gt;

&lt;p&gt;By then, I was a regular reader of the Ionic blog and used (borrowed?) an image of theirs for the post (Thanks again, Ionic!). It was well received and led to a bunch of native developers leaving many "spirited" comments. Ha!&lt;/p&gt;

&lt;p&gt;Later, after the iPhone X was released, I struggled to update my PhoneGap apps to support the infamous "notch." I eventually figured out a general solution then wrote another guest post for the PhoneGap blog. To date, &lt;a href="https://blog.phonegap.com/displaying-a-phonegap-app-correctly-on-the-iphone-x-c4a85664c493" rel="noopener noreferrer"&gt;"Displaying a PhoneGap App Correctly on the iPhone X"&lt;/a&gt; is my highest viewed piece of writing ever with over 223,000 views (the notch still confuses developers to this day!).&lt;/p&gt;

&lt;p&gt;My final post for the PhoneGap blog, &lt;a href="https://blog.phonegap.com/phonegap-devs-its-time-to-embrace-a-ui-framework-42e69a20ae" rel="noopener noreferrer"&gt;"PhoneGap Devs: It’s Time to Embrace a UI Framework"&lt;/a&gt; was a clear call to action to the community: pick a UI framework so that you can focus on building your app instead of dealing with various mobile issues (like the iPhone notch!). By that time, I was working for Ionic so naturally the article focused on the Ionic Framework.&lt;/p&gt;

&lt;p&gt;Huge thanks to the PhoneGap team for allowing me to guest on the blog!&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding my Dream Job at Ionic
&lt;/h2&gt;

&lt;p&gt;By the time 2018 rolled around, I was even deeper into web development, working regularly with Angular 2 and .NET Core at my day job. Angular, while initially challenging to learn, was a breathe of fresh air compared to my now-aging "PhoneGap stack." &lt;/p&gt;

&lt;p&gt;One night, I saw a tweet from the Ionic team:&lt;/p&gt;

&lt;blockquote data-lang="en"&gt;
&lt;p&gt;Calling all product champions! Do you have a technical background, but enjoy being the face and voice of the product, rather than sitting behind the scenes? Ionic is looking for a Senior Product Evangelist to join the team! Position can be remote! &lt;a href="https://t.co/lHQo6OHrcJ" rel="noopener noreferrer"&gt;https://t.co/lHQo6OHrcJ&lt;/a&gt;&lt;/p&gt;— ionic (@Ionicframework) &lt;a href="https://twitter.com/Ionicframework/status/986327763115302914?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;April 17, 2018&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;The timing was simply incredible: at that moment I was taking a break from packing up my apartment. I planned to move back to Madison, Wisconsin that Summer, where Ionic headquarters is located. Leveraging my PhoneGap guest blog posts, Pluralsight course, and hybrid app experience, I landed the role as a Product Evangelist/Developer Advocate. See the full story &lt;a href="https://www.netkow.com/celebrating-one-year-at-ionic/" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I started building PhoneGap apps, I had no idea what it would lead to. Hybrid app development has been such an incredibly rewarding career path. After years of hard work, some luck along the way, and support from an amazing community, I'm grateful to work on hybrid app development fulltime now, and for such an awesome company as Ionic.&lt;/p&gt;

&lt;h2&gt;
  
  
  PhoneGap's Legacy
&lt;/h2&gt;

&lt;p&gt;So as you can see, PhoneGap changed my life for the better. I owe a lot of my career to this amazing technology and the people that built it. But enough about me. 😀 &lt;/p&gt;

&lt;p&gt;Did PhoneGap succeed in its mission to make the web platform a first-class citizen?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We believe in a web open to everyone to participate however they will. No locked doors. No walls." &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Broadly speaking, PhoneGap absolutely succeeded: as pioneers of hybrid app development, they "solved" cross-platform app development challenge while also being incredibly influential in making the web a first-class development platform.&lt;/p&gt;

&lt;p&gt;In the time since it was created - over 12 years ago - we've seen the web platform explode in popularity: the vast majority of developers are web developers and many folks new to software development learn web dev first since it's so accessible and open.&lt;/p&gt;

&lt;p&gt;Sure, the web platform isn't perfect, but it's come a long way and will continue to evolve. It has matured a lot over the past few years, from modern JavaScript (ES6 and beyond) to package managers like npm, to built-in cross-platform browser APIs that provide rich user experiences, to the rise of Progressive Web Apps (PWAs) that fill the "gap" in "PhoneGap."&lt;/p&gt;

&lt;p&gt;Now, all of us at &lt;a href="https://ionic.io" rel="noopener noreferrer"&gt;Ionic&lt;/a&gt; are ready to carry the torch as the modern leader of cross-platform hybrid app development. Our native runtime tool &lt;a href="https://capacitorjs.com" rel="noopener noreferrer"&gt;Capacitor&lt;/a&gt;, as a spiritual successor to PhoneGap, offers a modern, web-first approach to hybrid and is &lt;a href="https://capacitorjs.com/docs/cordova" rel="noopener noreferrer"&gt;backward compatible&lt;/a&gt; with PhoneGap.&lt;/p&gt;

&lt;p&gt;Thank you to Adobe and the PhoneGap team for their hard work over the years and helping so many developers embrace web development. Long live the web platform!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ionic</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Hosting Ionic Meetups Online</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Wed, 15 Apr 2020 15:08:57 +0000</pubDate>
      <link>https://dev.to/ionic/hosting-ionic-meetups-online-51j</link>
      <guid>https://dev.to/ionic/hosting-ionic-meetups-online-51j</guid>
      <description>&lt;p&gt;A few weeks ago, we shared how the &lt;a href="https://ionicframework.com/blog/how-the-ionic-community-is-responding-to-the-covid-19-crisis/" rel="noopener noreferrer"&gt;Ionic community is tackling the COVID-19 crisis&lt;/a&gt; by building apps to help inform the public.&lt;/p&gt;

&lt;p&gt;The efforts are incredibly inspiring - while COVID has upended normal life and forced us inside, that doesn’t mean that the community’s desire to connect with other members or learn new skills has stopped. Quite the opposite, in fact.&lt;/p&gt;

&lt;p&gt;During the early days of Ionic, hosting meetups was a great way to connect with local community members, improve your web development skills, and keep up to date with the latest in Ionic tech. So, to help facilitate our community learning and growing together, we’re putting renewed effort into making Ionic Meetups easy and fun again.&lt;/p&gt;

&lt;p&gt;Using a variety of free online tools and resources, hosting a virtual Ionic meetup has never been easier. Here are a few resources and guides that we’ve put together to help you get started, starting with a list of pre-built presentations (including one that’s brand new for Ionic React), along with Meetup Pro support for qualified groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Ionic React Presentation, Powered by DeckDeckGo
&lt;/h2&gt;

&lt;p&gt;Launched late last year, the reception to &lt;a href="https://ionicframework.com/react" rel="noopener noreferrer"&gt;Ionic React&lt;/a&gt; has been wonderful. As part of our rebooted Ionic Meetup effort, we’ve created a brand new presentation on Ionic React. View &lt;a href="https://present-react.ionicframework.com" rel="noopener noreferrer"&gt;the online version here&lt;/a&gt; and &lt;a href="https://github.com/ionic-team/present-ionic-react" rel="noopener noreferrer"&gt;the code here&lt;/a&gt;. Take a look and let us know what you think in the comments.&lt;/p&gt;

&lt;p&gt;Staying true to our love of web technologies, the presentation is actually a standalone progressive web app (PWA) built using Web Components and &lt;a href="http://ionicframework.com" rel="noopener noreferrer"&gt;Ionic Framework&lt;/a&gt; components. Would you expect anything else? 🚀&lt;/p&gt;

&lt;p&gt;To pull this off, we didn’t have to look very far - a couple of talented Ionic Community members had already built an awesome solution: &lt;a href="https://deckdeckgo.com/" rel="noopener noreferrer"&gt;DeckDeckGo&lt;/a&gt;, an open source presentation creator. Needless to say, we’re big fans of David and Nicolas’ work: from running the &lt;a href="https://www.meetup.com/Ionic-Zurich/" rel="noopener noreferrer"&gt;Ionic Zurich&lt;/a&gt; Meetup group to &lt;a href="https://dev.to/daviddalbusco"&gt;blogging often&lt;/a&gt; about web development (and Stencil!), and even guesting &lt;a href="https://betontheweb.ionicframework.com/episodes/pwas-powered-by-web-components-with-david-dal-busco" rel="noopener noreferrer"&gt;on the Ionic Podcast&lt;/a&gt;. A big thank you from the Ionic team for creating such an awesome tool! 💙&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Ionic Presentations
&lt;/h3&gt;

&lt;p&gt;But wait, there’s more! We have additional presentations available as publicly viewable Google Slide decks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.google.com/presentation/d/1-AB6EXLCOPtoG5OlgVBNufxjLXq4A2_qyPLotMyIkzQ/edit?usp=sharing" rel="noopener noreferrer"&gt;What is Ionic?&lt;/a&gt; covers how to build mobile apps using web technology.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.google.com/presentation/d/16-HXhdxPf6wAL45iAVJWEwcjnJhUVakwCQoyeI--vcg/edit?usp=sharing" rel="noopener noreferrer"&gt;What is Capacitor?&lt;/a&gt; covers Ionic’s official native runtime used to package and deploy Ionic apps for various platforms including iOS, Android, Electron, and PWAs. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.google.com/presentation/d/1EQhgu0AW2GoWdLHqIUcKhaPzBJ6VLg86wURbyKqKp_I/edit#slide=id.p" rel="noopener noreferrer"&gt;What is Stencil?&lt;/a&gt; covers Ionic’s Web Component Compiler that makes it easy to create web components that run everywhere, without the need for a framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part about these presentations is that they’re easy to share informally with friends and co-workers. However, if you’re presenting to a group and you want something more official, we recommend virtual Meetups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Online Meetups, Powered by Meetup.com
&lt;/h2&gt;

&lt;p&gt;For a more “official” approach, Ionic recommends &lt;a href="https://www.meetup.com" rel="noopener noreferrer"&gt;Meetup.com&lt;/a&gt;. While in-person events are (understandably) on hold, you can still host an online Meetup.&lt;/p&gt;

&lt;p&gt;We encourage anyone that is interested in Ionic and web development in general to start an Ionic Meetup. &lt;/p&gt;

&lt;h3&gt;
  
  
  Register a new Meetup group
&lt;/h3&gt;

&lt;p&gt;When registering your new Ionic group, there are just a few details to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose relevant topics: Be sure to choose &lt;strong&gt;Ionic Framework&lt;/strong&gt;. Meetup automatically helps new groups find members by referring them to groups that match their chosen interests, so setting the right topics is key. Additional topics to set include: Hybrid Apps, Web Development, JavaScript, Angular, React, and Mobile Development.&lt;/li&gt;
&lt;li&gt;Group name: Keep it simple, such as “Ionic [City Name]”.&lt;/li&gt;
&lt;li&gt;Describe the group: Keep it light and friendly. A sample you can use:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Ionic [location] is centered around Ionic, a free, cross-platform, open source platform for building native, mobile, desktop, and progressive web apps. Come connect with web developers of all skill levels to share resources, learn, and discuss app development topics, including the latest new features of Ionic’s technology, web dev best practices, native enhancements, Progressive Web Apps, performance, and more.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Meetup also has a &lt;a href="https://www.meetup.com/blog/the-ultimate-guide-to-starting-a-group-on-meetup/" rel="noopener noreferrer"&gt;great guide&lt;/a&gt; on starting a new group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create your first event
&lt;/h3&gt;

&lt;p&gt;Online hosting is a great, low friction way to get started. As an added bonus, Meetup now makes it easy to &lt;a href="https://www.meetup.com/blog/how-to-host-an-online-event-on-meetup/" rel="noopener noreferrer"&gt;host an online event&lt;/a&gt; - simply click “Make this an online event” under Location then add the event link.&lt;/p&gt;

&lt;p&gt;Creating a presentation is hard work - so don’t bother! Pick a topic that you’re most comfortable with then leverage the relevant Ionic presentation template above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Promoting
&lt;/h3&gt;

&lt;p&gt;Start by inviting your friends and coworkers to your first Meetup, then expand from there.&lt;/p&gt;

&lt;p&gt;Additionally, send an email to &lt;a href="mailto:meetup@ionicframework.com"&gt;meetup@ionicframework.com&lt;/a&gt; with the link to your new Meetup group. You can also share your link on social media - tag us on Twitter (&lt;a href="https://twitter.com/IonicFramework" rel="noopener noreferrer"&gt;@IonicFramework&lt;/a&gt;) and we’ll help spread the word about your new group!&lt;/p&gt;

&lt;h3&gt;
  
  
  Become an Official Ionic Meetup
&lt;/h3&gt;

&lt;p&gt;The most active Ionic Meetups are eligible to earn Official status (see the official group list &lt;a href="https://www.meetup.com/pro/ionic/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Meetup charges Organizers a small monthly fee. However, when you become an official Ionic Meetup, we’ll not only pay Meetup’s costs, but you’ll also unlock periodic access to Ionic swag. Simply host 3 consistent Meetups then send us an email (&lt;a href="mailto:meetup@ionicframework.com"&gt;meetup@ionicframework.com&lt;/a&gt;) for next steps.&lt;/p&gt;

&lt;p&gt;For Official groups looking to relaunch their meetup, we’ve created new event templates, featuring each of the Ionic presentations mentioned above. Choose one after clicking “Schedule an Event.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Host an Ionic Meetup Today
&lt;/h2&gt;

&lt;p&gt;With the entire world hanging out online, it’s never been a better time to connect with the web development community. Consider starting an Ionic Meetup today, and let us know what you think of the presentations in the comments below. What others should we create? 🍻&lt;/p&gt;

&lt;h2&gt;
  
  
  Join us Tuesday, April 21st
&lt;/h2&gt;

&lt;p&gt;To kick off the Ionic Online Meetup movement, I’ll be presenting on &lt;a href="https://ionicframework.com/react" rel="noopener noreferrer"&gt;Ionic React&lt;/a&gt; in partnership with Ionic Zürich. Come learn about Ionic’s support for this popular web framework, then stay for a live Q&amp;amp;A session afterward.&lt;/p&gt;

&lt;p&gt;Of course, it’s online - anyone can join us!  &lt;a href="https://www.meetup.com/Ionic-Zurich/events/270043580/" rel="noopener noreferrer"&gt;RSVP here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>meetup</category>
      <category>react</category>
    </item>
    <item>
      <title>Announcing Capacitor 2.0</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Wed, 08 Apr 2020 17:10:36 +0000</pubDate>
      <link>https://dev.to/ionic/announcing-capacitor-2-0-12a2</link>
      <guid>https://dev.to/ionic/announcing-capacitor-2-0-12a2</guid>
      <description>&lt;p&gt;Today I’m thrilled to announce the 2.0 release of Capacitor, Ionic’s native runtime that makes it easy to build web apps that run on iOS, Android, and on the web as Progressive Web Apps— all powered by a single codebase.&lt;/p&gt;

&lt;p&gt;Developers use Capacitor as a native app container for packaging and deploying their Ionic apps to various mobile and desktop platforms. Capacitor allows them to access native features like the Camera using the same code across all platforms - without having to worry about platform-specific details.&lt;/p&gt;

&lt;p&gt;This new version updates Capacitor and its project templates to the latest security, bug fixes, and features including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swift 5 and Xcode 11+ support&lt;/li&gt;
&lt;li&gt;Android 10 (SDK 29) and AndroidX support, which makes Face Unlock and Iris Unlock available now in Ionic &lt;a href="https://ionicframework.com/enterprise/identity-vault" rel="noopener noreferrer"&gt;Identity Vault&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Bug fixes and usability improvements to 23+ core plugins&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Capacitor?
&lt;/h2&gt;

&lt;p&gt;Traditionally, web developers have turned to tools like Apache Cordova/Adobe PhoneGap to deploy their web apps to other platforms. This worked well for many years, with Ionic recommending Cordova as the default method for targeting native iOS and Android platforms. Over time our opinions changed about how this layer should work and after exploring various ideas, determined the best option was to bring the native runtime component of app building in-house. &lt;/p&gt;

&lt;p&gt;Now a key part of the Ionic Platform, Capacitor makes it easy for web developers to reuse their skills to build quality apps for all platforms, while significantly lessening the likelihood that they’ll get stuck on native-specific issues. You can learn more about how Capacitor compares with Cordova &lt;a href="https://ionicframework.com/resources/articles/capacitor-vs-cordova-modern-hybrid-app-development" rel="noopener noreferrer"&gt;in this article penned by Max&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Capacitor 2.0 updates its underlying technology to support the latest versions of programming languages and operating systems that power all Capacitor apps, enabling improved performance, security, and modern development experiences. Specifically, support for the latest tech from iOS and Android is now available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swift 5 and Xcode 11+
&lt;/h2&gt;

&lt;p&gt;On the iOS front, this means &lt;a href="https://www.raywenderlich.com/55728-what-s-new-in-swift-5" rel="noopener noreferrer"&gt;Swift 5&lt;/a&gt; and Xcode 11+ support. This brings smaller app bundle sizes, modern development tooling and features, and compatibility with earlier versions of Swift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 10 (SDK 29) and AndroidX
&lt;/h2&gt;

&lt;p&gt;On Android, this means Android 10 (aka SDK 29), with improved security and biometrics, audio/video capabilities, and system-wide dark mode. &lt;/p&gt;

&lt;p&gt;Additionally, AndroidX, the next generation of the Android Support Library, is now supported. Both provide Android developers with a standard way to provide users with newer features on earlier versions of Android or graceful fallback when unavailable.&lt;/p&gt;

&lt;p&gt;AndroidX replaces the Support Library, providing feature parity and backward-compatibility while also bringing improvements to library modularity, smaller code size, and a better developer experience.&lt;/p&gt;

&lt;p&gt;Whether you’re a consumer of Capacitor or a &lt;a href="https://capacitor.ionicframework.com/docs/plugins/" rel="noopener noreferrer"&gt;plugin author&lt;/a&gt;, you simply need to update your projects (or CLI) to access these new capabilities (see below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved Plugins, Tooling, and Docs
&lt;/h2&gt;

&lt;p&gt;Capacitor 1.0 wouldn’t have been as successful as it has been without a solid developer experience backing it. With Capacitor 2.0, we’ve reviewed it from head to toe, leading to  updates to the core plugins, the CLI tooling, and documentation.&lt;/p&gt;

&lt;p&gt;Fundamentally, if you’re going to build unique app experiences, you need a reliable set of plugins to build on top of. Capacitor 2.0 includes many updates to its &lt;a href="https://capacitor.ionicframework.com/docs/apis" rel="noopener noreferrer"&gt;23 core plugins&lt;/a&gt;, from bug fixes to new features to usability improvements. Many of these changes were driven by feedback from you, the Ionic community, so thank you! We appreciate your efforts and feedback - it helps us ensure that it’s never been easier to get started with Capacitor.&lt;/p&gt;

&lt;p&gt;Speaking of awesome community efforts, Ionic’s official tool for generating splash screens and icons, &lt;code&gt;cordova-res&lt;/code&gt;, just got a big update: Capacitor support! Thanks to wannadream for &lt;a href="https://github.com/ionic-team/cordova-res/pull/85" rel="noopener noreferrer"&gt;this contribution&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; cordova-res
cordova-res &lt;span class="nt"&gt;--skip-config&lt;/span&gt; &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this expanding scope, I guess we’ll have to &lt;a href="https://github.com/ionic-team/cordova-res/issues/99" rel="noopener noreferrer"&gt;rename it&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Capacitor’s embrace of native tooling means that it’s never been easier to implement more creative native features. That said, the nuances and details can be challenging, so we’ve begun to add new implementation guides to the Capacitor docs, including &lt;a href="https://capacitor.ionicframework.com/docs/guides/deep-links/" rel="noopener noreferrer"&gt;Deep Links&lt;/a&gt;, &lt;a href="https://ionicframework.com/blog/adding-sign-in-with-apple-to-an-ionic-app/" rel="noopener noreferrer"&gt;Sign In with Apple&lt;/a&gt; and a refreshed &lt;a href="https://capacitor.ionicframework.com/docs/guides/push-notifications-firebase" rel="noopener noreferrer"&gt;Push Notifications with Firebase&lt;/a&gt; guide. There are more coming soon - if there’s content you think is missing, please let us know in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign In with Apple
&lt;/h2&gt;

&lt;p&gt;Sign in with Apple offers users the ability to sign into apps and websites using their Apple ID. Benefits include a focus on security (automatic two-factor authentication and user activity is not tracked) and users can start using your app right away (bypassing traditional sign-up measures).&lt;/p&gt;

&lt;p&gt;As of April 2020, apps that use a third-party or social login service are required to offer Sign in with Apple as an option as well. This includes Facebook, Twitter, Linkedin, and Google. &lt;/p&gt;

&lt;p&gt;The community-driven Capacitor Sign in with Apple plugin offers an easy implementation process out of the box. Simply install the plugin, configure the native iOS project, then move on. Learn how &lt;a href="https://ionicframework.com/blog/adding-sign-in-with-apple-to-an-ionic-app/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android Face Unlock &amp;amp; Iris Unlock
&lt;/h2&gt;

&lt;p&gt;In order to protect your user’s data, you need to keep up with the latest mobile security features. Security is traditionally challenging to implement correctly - with disastrous results if done wrong.&lt;/p&gt;

&lt;p&gt;With the new support of AndroidX in Capacitor 2.0, &lt;a href="https://ionicframework.com/enterprise/identity-vault" rel="noopener noreferrer"&gt;Ionic Identity Vault&lt;/a&gt; now supports Android’s Face Unlock and Iris Unlock. This update to Ionic’s all-in-one frontend identity management system brings industry-leading facial and iris authentication features to the Android platform. Using a simple API, you can add top of the line biometric authentication to your Capacitor apps. &lt;a href="https://ionicframework.com/enterprise/identity-vault#demo" rel="noopener noreferrer"&gt;Learn more here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Soon: CORS is no more!
&lt;/h2&gt;

&lt;p&gt;Speaking of listening to dev feedback - we’ve got a new plugin in the works that should alleviate most of the pain Ionic developers feel when accessing external data &amp;amp; APIs on mobile. &lt;/p&gt;

&lt;p&gt;Cross-Origin Resource Sharing (CORS) is a mechanism that browsers and webviews — like the ones powering Capacitor — use to protect your user's data and prevent attacks that would compromise your app.&lt;/p&gt;

&lt;p&gt;While great for security, this commonly restricts HTTP/S requests - the mechanism through which developers access and manage external data - leading to lots of confusion. Ultimately, this is a distraction that takes away time and focus better spent building apps.&lt;/p&gt;

&lt;p&gt;Soon, we’ll introduce a new HTTP plugin that addresses this automatically in a cross-platform approach. On mobile, HTTP requests are executed natively, outside the webview. This effectively bypasses CORS, resulting in an improved developer experience while maintaining proper security protocols (native apps are not subjected to CORS). Web requests use fetch, the modern web browser API for retrieving external resources.&lt;/p&gt;

&lt;p&gt;The HTTP plugin is in &lt;a href="https://github.com/ionic-team/capacitor/pull/2495" rel="noopener noreferrer"&gt;active development&lt;/a&gt;. We’d love for you to test it and provide feedback. You can also check out our &lt;a href="https://ionicframework.com/docs/troubleshooting/cors" rel="noopener noreferrer"&gt;CORS troubleshooting guide&lt;/a&gt; in the meantime.&lt;/p&gt;

&lt;h2&gt;
  
  
  An update on Electron support
&lt;/h2&gt;

&lt;p&gt;After taking a hard look at what our priorities and focus needs to be in 2020, we've decided to put Electron support back into beta. After additional investment in key platforms that generate the most interest (iOS, Android, and PWAs), we'll revisit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Update Process
&lt;/h2&gt;

&lt;p&gt;Capacitor gives Ionic developers complete control of their native projects. Among &lt;a href="https://capacitor.ionicframework.com/docs/cordova#native-project-management" rel="noopener noreferrer"&gt;many other benefits&lt;/a&gt;, this makes updating easy and straightforward.&lt;/p&gt;

&lt;p&gt;First, update Capacitor Core and the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;your-app-folder
npm &lt;span class="nb"&gt;install&lt;/span&gt; @capacitor/cli@latest
npm &lt;span class="nb"&gt;install&lt;/span&gt; @capacitor/core@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, update each Capacitor library in use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @capacitor/ios@latest
npx cap &lt;span class="nb"&gt;sync &lt;/span&gt;ios

npm &lt;span class="nb"&gt;install&lt;/span&gt; @capacitor/android@latest
&lt;span class="c"&gt;# Within Android Studio, click “Sync Project with Gradle Files” button&lt;/span&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;electron
npm &lt;span class="nb"&gt;install&lt;/span&gt; @capacitor/electron@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, follow these update instructions which cover one-time manual steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://capacitor.ionicframework.com/docs/ios/updating/#from-1-5-1-to-2-0-0" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://capacitor.ionicframework.com/docs/android/updating/#from-1-5-1-to-2-0-0" rel="noopener noreferrer"&gt;Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://capacitor.ionicframework.com/docs/electron/updating/#from-1-5-2-to-2-0-0" rel="noopener noreferrer"&gt;Electron&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ionic-team/capacitor/releases/tag/2.0.0" rel="noopener noreferrer"&gt;Breaking Changes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  More than a version number
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://capacitor.ionicframework.com/" rel="noopener noreferrer"&gt;Capacitor 2.0&lt;/a&gt; is a significant update to an increasingly important part of Ionic’s app development platform.&lt;/p&gt;

&lt;p&gt;We’ve been blown away by the reception of Capacitor since it was announced, and installs have been &lt;a href="https://npmcharts.com/compare/@capacitor/core" rel="noopener noreferrer"&gt;growing quickly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With 2.0, we are starting to make Capacitor the default for all new Ionic React and soon Ionic Angular projects, and expect us to be recommending Capacitor for all new Ionic apps and increasingly for enterprise apps as well.&lt;/p&gt;

&lt;p&gt;Additionally, we are dedicating more internal resources to Capacitor as it becomes a key part of the Ionic offering. Expect to see Capacitor receive a lot more focus and attention from us in the coming months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start building today
&lt;/h2&gt;

&lt;p&gt;If you’re new to Capacitor, it’s easy to get started. Follow our complete &lt;a href="https://ionicframework.com/docs/intro/next" rel="noopener noreferrer"&gt;First App guide&lt;/a&gt; to build a Photo Gallery app powered by the Capacitor Camera, Filesystem, and Storage APIs. For those looking to dive in right away, check out the &lt;a href="https://ionicframework.com/start" rel="noopener noreferrer"&gt;Ionic App Wizard&lt;/a&gt; - generate a great Capacitor starter app in seconds.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>android</category>
      <category>opensource</category>
      <category>ios</category>
    </item>
    <item>
      <title>Thanks to Capacitor, I’ve fallen in love with mobile development again</title>
      <dc:creator>Matt Netkow</dc:creator>
      <pubDate>Fri, 13 Mar 2020 13:37:00 +0000</pubDate>
      <link>https://dev.to/ionic/thanks-to-capacitor-i-ve-fallen-in-love-with-mobile-development-again-17oc</link>
      <guid>https://dev.to/ionic/thanks-to-capacitor-i-ve-fallen-in-love-with-mobile-development-again-17oc</guid>
      <description>&lt;p&gt;Long before joining Ionic, I &lt;a href="https://www.netkow.com/categories/phonegap" rel="noopener noreferrer"&gt;built web-based apps&lt;/a&gt; (using jQuery and Knockout.js!) and deployed them to iOS and Android using Cordova. They weren’t pretty (I didn’t have something &lt;a href="https://ionicframework.com/docs/components" rel="noopener noreferrer"&gt;like this&lt;/a&gt; 😉 available), the code was messy, but they got the job done: I was a web developer building mobile apps using one codebase!&lt;/p&gt;

&lt;p&gt;Despite my enthusiasm, I quickly ran into issues that would continue to haunt me over time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited cross-platform deployment:&lt;/strong&gt; I wanted to make my apps available on iOS, Android, and the web. Cordova’s focus on mobile, as well as limited browser APIs, made it challenging, if not impossible, to reach all platforms successfully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opaque native configuration:&lt;/strong&gt; Builds would fail or features wouldn’t work as expected, and I struggled to solve them since I didn’t understand Cordova’s native project abstractions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stability:&lt;/strong&gt; I dreaded updating the apps because native plugins would constantly break between new mobile OS versions or conflicting plugin versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those were dark times. However, I’ve recently been building a new Real App™️ using Capacitor and well, it’s made me fall in love with mobile all over again. In this post, I’ll cover how Capacitor solves all of these issues, including cross-platform support, easy native configuration, long-term stability, and the built-in Cordova migration support.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Side-note: If you’re not already familiar with Capacitor, you can find a ton of info &lt;a href="https://ionicframework.com/blog/announcing-capacitor-1-0/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://capacitor.ionicframework.com/docs/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. TL;DR - it’s a new native runtime for building cross-platform mobile, desktop, and web apps, built by the Ionic team. You can think of it as a spiritual successor to Cordova that applies many hard-earned lessons from the last 10 years of hybrid mobile development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And now, let’s review how Capacitor applies those lessons, resulting in a much-improved developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Mobile
&lt;/h2&gt;

&lt;p&gt;Cordova’s focus on mobile, coupled with limited web browser APIs, made it challenging, if not impossible, to reach all platforms with a single codebase successfully.&lt;/p&gt;

&lt;p&gt;Recognizing this, Capacitor embraces a web-first approach with its Core APIs, meaning they work on the web, iOS, Android, and desktop. Since they provide access to commonly needed functionality, they cover much of the core Cordova plugins while also including some new features.&lt;/p&gt;

&lt;p&gt;The Capacitor Camera API is a great example. With a single method call, you can take a photo with the device’s camera on the web, iOS, and Android:&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;Plugins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CameraResultType&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;@capacitor/core&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="nx"&gt;Camera&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;takePicture&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;image&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;Camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPhoto&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resultType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CameraResultType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uri&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;imageElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webPath&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;That said, what about features that aren’t available on the web? In those cases, &lt;a href="https://capacitor.ionicframework.com/docs/plugins/web" rel="noopener noreferrer"&gt;web plugins&lt;/a&gt; can be built to act as a fallback. When in doubt, check &lt;a href="https://caniuse.com/" rel="noopener noreferrer"&gt;Can I Use&lt;/a&gt; to see what’s possible.&lt;/p&gt;

&lt;p&gt;Additionally, I was thrilled to learn that browser APIs have evolved to become more feature-rich since I began building hybrid apps years ago. As you can see from my favorite reference site, &lt;a href="https://whatwebcando.today/" rel="noopener noreferrer"&gt;What Web Can Do Today&lt;/a&gt;, device integration is more powerful than ever. Everything from Bluetooth to offline storage to virtual/augmented reality is available today.&lt;/p&gt;

&lt;p&gt;Pairing Capacitor with these new browser APIs, I could build my app quickly in the browser like before, while also ensuring true cross-platform deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Native Project Configuration
&lt;/h2&gt;

&lt;p&gt;Cordova leverages a single configuration file that abstracts away native project details from the developer, which is great for managing all of your configurations together. However, when project builds fail or features don’t work as expected, it’s difficult to understand what the issue is and where it’s occurring (is it Cordova tooling or native project code?) since any applied changes are a black box to web developers. As a result, it’s too easy to get stuck on a problem completely unrelated to app development.&lt;/p&gt;

&lt;p&gt;Capacitor takes the opposite approach, fully embracing configuration via native IDEs. There are two steps to implementing a native mobile feature with Capacitor: configuring the native project and handling the feature “the Capacitor way” within the app’s code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native Project Management
&lt;/h3&gt;

&lt;p&gt;I’ll admit that I was initially skeptical about Capacitor’s approach to native project management. Despite Cordova's issues, I &lt;em&gt;liked&lt;/em&gt; having a single configuration file to manage my native iOS and Android projects. Moving to Capacitor meant managing the native projects myself. Naturally, this was intimidating because I thought the whole point of the hybrid app approach was to avoid learning native app development. How much time would this take to learn? &lt;em&gt;Ugh&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;After trying it though, I was pleasantly surprised. Despite being only somewhat familiar with the native IDEs (Xcode and Android Studio), it turns out that the learning curve is quite small. You can go as shallow or deep into them as needed. Most of the time you just make small manual changes to &lt;code&gt;AndroidManifest.xml&lt;/code&gt; (Android) or &lt;code&gt;Info.plist&lt;/code&gt; (iOS).&lt;/p&gt;

&lt;p&gt;When implementing complex mobile features (think: deep links, OAuth), you research the topic (example: “ios deep links” leads you to Apple’s &lt;a href="https://developer.apple.com/ios/universal-links/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) and follow the exact steps from the official documentation. Unlike Cordova, which abstracts these details away from you, features are implemented using the same instructions a native developer follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing Features
&lt;/h3&gt;

&lt;p&gt;Once configuration is complete, implementing the feature “the Capacitor way” isn’t all that challenging or “custom.” Depending on the use case, this could mean using a Capacitor Core API, a community plugin, or simply regular code. The effort varies, but generally, it’s straightforward.&lt;/p&gt;

&lt;p&gt;As a bonus, if you do learn native mobile development someday (or &lt;a href="https://capacitor.ionicframework.com/docs/plugins" rel="noopener noreferrer"&gt;build a Capacitor plugin&lt;/a&gt;), you’ll be better prepared because you understand the native ecosystem already.&lt;/p&gt;

&lt;p&gt;Regardless of which cross-platform solution you choose, you have to learn mobile concepts anyway. Why not learn them the right way?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stability
&lt;/h2&gt;

&lt;p&gt;While we usually look forward to new software features and improvements, I dreaded updating my Cordova apps. Native plugins would constantly break between new mobile OS versions and conflicting plugin versions would mean I could update one plugin but not the other. Without a native development background, I got stuck often, forced to post on online forums and just hope for an answer.&lt;/p&gt;

&lt;p&gt;While Capacitor doesn’t fully resolve the above challenges, it does a great job of equipping you to handle them. After just a bit of time developing apps with Capacitor, I feel more confident implementing features directly in each native project, as well as in  Capacitor’s long-term stability as well.&lt;/p&gt;

&lt;p&gt;Given that Capacitor gives you full control over native project management, many native features don’t require a plugin anymore (like deep linking - new guide coming soon!), so app size is reduced and performance is improved. Less code to maintain (especially if it’s not yours!) is a huge plus.&lt;/p&gt;

&lt;p&gt;Also, most app features are configured once, then never touched again. And, given Apple and Google’s strong attention to backward compatibility, it could be years (if ever) before you need to make changes.&lt;/p&gt;

&lt;p&gt;When you do run into issues while developing an app, it’s easy to search online for the answer. With no abstraction layer in place, you can search for and follow the answer as a native developer would. Personally, I feel much more confident in my ability to make changes and not get stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration: Moving from Cordova to Capacitor
&lt;/h2&gt;

&lt;p&gt;If by now you’re sold on giving Capacitor a try, you’ll be thrilled to learn that Capacitor has built-in Cordova migration support, designed to make the process as seamless as possible. Here’s a sampling of what it offers.&lt;/p&gt;

&lt;p&gt;When you add a new platform (iOS, Android, etc.) to the project, a warning appears if an &lt;a href="https://capacitor.ionicframework.com/docs/cordova/known-incompatible-plugins" rel="noopener noreferrer"&gt;incompatible plugin&lt;/a&gt; is found. Most of the time, this is because Capacitor has an equivalent core plugin, or it simply isn’t needed anymore. Here’s &lt;code&gt;cordova-plugin-splash-screen&lt;/code&gt; after running &lt;code&gt;ionic cap add ios&lt;/code&gt; for example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Found 1 incompatible Cordova plugin for ios, skipped install&lt;br&gt;
    cordova-plugin-splashscreen (5.0.2)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also, if you install additional Cordova plugins at any time, then sync the project (this updates the native platforms and their dependencies), Capacitor tells you what you need to do with Cordova plugins that are supported but need additional native project configuration. Here’s the &lt;a href="https://ionicframework.com/docs/enterprise/deeplinks" rel="noopener noreferrer"&gt;deep links plugin&lt;/a&gt; warning, for example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;$ ionic cap sync&lt;br&gt;
[warn] Plugin @ionic-enterprise/deeplinks might require you to add&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;CFBundleURLSchemes&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;$URL_SCHEME&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;in the existing CFBundleURLTypes entry of your Info.plist to work&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cordova preferences are migrated over, too. When Capacitor is added to an existing Cordova project, it reads the &lt;code&gt;&amp;lt;preferences&amp;gt;&lt;/code&gt; in &lt;code&gt;config.xml&lt;/code&gt; and brings them into &lt;code&gt;capacitor.config.json&lt;/code&gt;. You can manually add more preferences to the &lt;code&gt;cordova.preferences&lt;/code&gt; object, too.&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;capacitor.config.json&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;"cordova"&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;"preferences"&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;"ScrollEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"android-minSdkVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"19"&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;This is just a sampling of how smooth the migration process is. See complete migration details &lt;a href="https://capacitor.ionicframework.com/docs/cordova/migrating-from-cordova-to-capacitor" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;We’ve come a long way since I started building hybrid mobile apps years ago. These days, I’m more productive than ever and loving mobile development again.&lt;/p&gt;

&lt;p&gt;What’s been your experience with Capacitor been like so far? Let us know in the comments below. If you haven't tried out Capacitor yet and you’d like to give it a try, check out our &lt;a href="https://ionicframework.com/docs/intro/first-app" rel="noopener noreferrer"&gt;new tutorial here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>capacitor</category>
      <category>cordova</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
