<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sebastian Rindom</title>
    <description>The latest articles on Forem by Sebastian Rindom (@sebrindom).</description>
    <link>https://forem.com/sebrindom</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%2F693078%2F6c6d4139-0ed7-478f-bee7-389c0a8c71d7.jpeg</url>
      <title>Forem: Sebastian Rindom</title>
      <link>https://forem.com/sebrindom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sebrindom"/>
    <language>en</language>
    <item>
      <title>Medusa: A New Way to Build Commerce</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Tue, 04 Apr 2023 17:57:23 +0000</pubDate>
      <link>https://forem.com/medusajs/medusa-a-new-way-to-build-commerce-4hia</link>
      <guid>https://forem.com/medusajs/medusa-a-new-way-to-build-commerce-4hia</guid>
      <description>&lt;p&gt;I am very excited to share an update to our website and announce our v1.8 release that came out today.&lt;/p&gt;

&lt;p&gt;Medusa is a new way to build commerce, and our updated website and the release clarifies this with a sleek new design and powerful new features.&lt;/p&gt;

&lt;p&gt;We build tools and modules that run in new, modern environments and make it easy for developers to create unique experiences.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://medusajs.com/readme/" rel="noopener noreferrer"&gt;our new website&lt;/a&gt; to read about why we started Medusa and where we are headed.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://medusajs.com/recap/" rel="noopener noreferrer"&gt;Recap page on our website&lt;/a&gt; to get a peak of all the cool new features we put out in 1.8. This post also highlights some of those features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Warehouse
&lt;/h2&gt;





&lt;p&gt;The Multi-Warehouse feature allows businesses to manage their inventory across multiple locations and sales channels, reserving items in orders that aren’t fulfilled yet, choose where to fulfill items from and where to return items to, and more!&lt;/p&gt;

&lt;p&gt;Learn more about Multi-Warehouse in the &lt;a href="https://medusajs.com/blog/announcing-multi-warehouse/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product Categories
&lt;/h2&gt;





&lt;p&gt;The Product Categories feature allows businesses to organize their products in categories of unlimited hierarchy. This improves product organization and allows customers to find products more easily.&lt;/p&gt;

&lt;p&gt;Learn more about Product Categories in the &lt;a href="https://medusajs.com/blog/announcing-product-categories/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Admin Plugin
&lt;/h2&gt;

&lt;p&gt;We previously shipped the Medusa admin in an isolated GitHub repository. This caused a lot of issues related to updating and extending it.&lt;/p&gt;

&lt;p&gt;So, we now ship the Medusa admin as an NPM package that can be installed as a plugin on the Medusa backend. This ensures that the admin now follows versioning similar to the Medusa core, and opens new potentials into its development and deployment.&lt;/p&gt;

&lt;p&gt;Learn more about the Admin Plugin in the &lt;a href="https://medusajs.com/blog/announcing-admin-plugin" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modules
&lt;/h2&gt;

&lt;p&gt;The Modules API gives developers more customization capabilities, allowing them to change how commerce features, or core logic is implemented. The Multi-warehouse feature is actually composed of two modules: inventory and stock location. Another example of modules are the cache and event bus modules.&lt;/p&gt;

&lt;p&gt;You can learn more about Modules in the &lt;a href="https://medusajs.com/blog/announcing-modules/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache Module
&lt;/h2&gt;

&lt;p&gt;The cache module is used to temporarily store or cache data such as product prices and tax rates.&lt;/p&gt;

&lt;p&gt;As the cache functionality is handled with a module, developers have the freedom to choose whichever third-party service or logic they used to handle caching. Medusa also provides two cache modules: local cache and Redis cache modules.&lt;/p&gt;

&lt;p&gt;You can learn more about Cache Modules in the &lt;a href="https://medusajs.com/blog/announcing-cache-modules/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Event Bus Module
&lt;/h2&gt;

&lt;p&gt;Medusa previously required using Redis for its event system. In this new release, the event system has been refactored to instead use an event bus module to handle the messaging system in Medusa.&lt;/p&gt;

&lt;p&gt;Developers can create their own module to integrate their preferred third-party pub/sub service or custom logic. Medusa also provides two event bus modules: a local event bus module and a Redis event bus module.&lt;/p&gt;

&lt;p&gt;You can learn more about the Event Bus Module in the &lt;a href="https://medusajs.com/blog/announcing-event-bus-modules/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transaction Orchestrator
&lt;/h2&gt;

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

&lt;p&gt;Transaction Orchestrator is a new feature in Medusa that allows handing transactions in an increasingly complex system and environment. This feature is a huge leap for Medusa to become more composable and robust, and it opened the door for the introduction and usage of modules.&lt;/p&gt;

&lt;p&gt;You can learn more about the Transaction Orchestrator in the &lt;a href="https://medusajs.com/blog/announcing-transaction-orchestrator/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAS Codegen Tools
&lt;/h2&gt;

&lt;p&gt;At Medusa, we use OpenApi Spec (OAS) to generate SDKs and automate documentation, among other features. The new OAS tool allows developers to generate their own OAS files and TypeScript client types from their Medusa backend.&lt;/p&gt;

&lt;p&gt;You can learn more about OAS Codegen Tools in the &lt;a href="https://medusajs.com/blog/announcing-openapi-spec-codegen-tool/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Documentation
&lt;/h2&gt;

&lt;p&gt;As mentioned in the beginning of this article, Medusa’s main goal has always been to provide the best developer experience. An essential part of that is constantly ensuring that we’re providing a reliable documentation with good user experience.&lt;/p&gt;

&lt;p&gt;Earlier this month we’ve launched a new documentation with a new structure and design. You can learn more about the process behind that in the &lt;a href="https://medusajs.com/blog/announcing-medusa-new-documentation/" rel="noopener noreferrer"&gt;product announcement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interested in Learning More about Medusa?
&lt;/h2&gt;

&lt;p&gt;If you’re interested in Medusa’s vision, and you want to learn more about it, check out our &lt;a href="https://medusajs.com/" rel="noopener noreferrer"&gt;website&lt;/a&gt; for all the information you need. You can also refer to our GitHub Discussions to learn more about our &lt;a href="https://github.com/medusajs/medusa/discussions/categories/roadmap" rel="noopener noreferrer"&gt;Roadmap&lt;/a&gt; or give us any feedback you have. In addition, you can join our &lt;a href="https://discord.gg/medusajs" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; and be part of our community.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>news</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Medusa’s New Feature: Order Editing</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Wed, 25 Jan 2023 14:13:55 +0000</pubDate>
      <link>https://forem.com/medusajs/medusas-new-feature-order-editing-2ibl</link>
      <guid>https://forem.com/medusajs/medusas-new-feature-order-editing-2ibl</guid>
      <description>&lt;p&gt;&lt;a href="https://medusajs.com/" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; is happy to announce Order Editing and Payment Collections as the latest additions to the Order API, strengthening one of the most critical parts of any commerce application. This is an essential step in our mission to deliver commerce building blocks that developers can use to create bespoke digital commerce experiences without reinventing core commerce functionality.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/medusajs" rel="noopener noreferrer"&gt;
        medusajs
      &lt;/a&gt; / &lt;a href="https://github.com/medusajs/medusa" rel="noopener noreferrer"&gt;
        medusa
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The world's most flexible commerce platform.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a href="https://www.medusajs.com" rel="nofollow noopener noreferrer"&gt;
  
    
    
    &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F59018053%2F229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg" class="article-body-image-wrapper"&gt;&lt;img alt="Medusa logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F59018053%2F229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg"&gt;&lt;/a&gt;
    
  &lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
  Medusa
&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;
  &lt;a href="https://docs.medusajs.com" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt; |
  &lt;a href="https://www.medusajs.com" rel="nofollow noopener noreferrer"&gt;Website&lt;/a&gt;
&lt;/h4&gt;
&lt;/div&gt;

&lt;p&gt;
  Building blocks for digital commerce
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/medusajs/medusa/blob/develop/LICENSE" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/6581c31c16c1b13ddc2efb92e2ad69a93ddc4a92fd871ff15d401c4c6c9155a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="Medusa is released under the MIT license."&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/medusajs/medusa/blob/develop/CONTRIBUTING.md" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/11c502cb0edd6eac274e462c7a70981ee26fde99043dba967b732d371efa2b87/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5052732d77656c636f6d652d627269676874677265656e2e7376673f7374796c653d666c6174" alt="PRs welcome!"&gt;
  &lt;/a&gt;
 &lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://twitter.com/intent/follow?screen_name=medusajs" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/6c164f14e4a24c801d3396eff84dbc5a7d8a3a6458b1c09ebbaaa0006d244a8c/68747470733a2f2f696d672e736869656c64732e696f2f747769747465722f666f6c6c6f772f6d65647573616a732e7376673f6c6162656c3d466f6c6c6f77253230406d65647573616a73" alt="Follow @medusajs"&gt;
  &lt;/a&gt;&lt;a href="https://discord.gg/medusajs" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/4921ed6603bc6780b3892f60abb1cd1143568cf1595701546c32f7a2619d9daf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636861742d6f6e253230646973636f72642d3732383944412e737667" alt="Discord Chat"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Visit the &lt;a href="https://docs.medusajs.com/learn" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt; to set up a Medusa application.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is Medusa&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Medusa is an ecommerce platform with a built-in framework for customization that allows you to build custom commerce applications without reinventing core commerce logic. The framework and modules can be used to build advanced B2B or DTC ecommerce stores, marketplaces, PoS systems, service businesses, or any product that needs foundational commerce primitives. All commerce modules are open-source and freely available on npm.&lt;/p&gt;

&lt;p&gt;Learn more about &lt;a href="https://docs.medusajs.com/learn/advanced-development/architecture/overview" rel="nofollow noopener noreferrer"&gt;Medusa’s architecture&lt;/a&gt; and &lt;a href="https://docs.medusajs.com/resources/commerce-modules" rel="nofollow noopener noreferrer"&gt;commerce modules&lt;/a&gt; in the Docs.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Upgrades &amp;amp; Integrations&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Follow the &lt;a href="https://github.com/medusajs/medusa/releases" rel="noopener noreferrer"&gt;Release Notes&lt;/a&gt; to keep your Medusa project up-to-date.&lt;/p&gt;

&lt;p&gt;Check out all &lt;a href="https://docs.medusajs.com/resources/integrations" rel="nofollow noopener noreferrer"&gt;available Medusa integrations&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Community &amp;amp; Contributions&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;The community and core team are available in &lt;a href="https://github.com/medusajs/medusa/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;, where you can ask for support, discuss roadmap, and share ideas.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://github.com/medusajs/medusa/blob/develop/CONTRIBUTING.md" rel="noopener noreferrer"&gt;Contribution Guide&lt;/a&gt; describes how to contribute to the…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/medusajs/medusa" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The Order Editing and Payment Collection features solve the problems faced when businesses need to change already placed orders. Systems that rely on order data, like fulfillment and accounting systems, will have to be updated, and payment discrepancies may arise. These updates and discrepancies often require error-prone, manual work. &lt;/p&gt;

&lt;p&gt;Medusa’s new capabilities offer a simple and worry-free approach to creating order edits while giving your customers the best experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Order Editing Works
&lt;/h2&gt;

&lt;p&gt;The Order Edit flow consists of three simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the changes you wish to make.&lt;/li&gt;
&lt;li&gt;Ask the customer to confirm your changes or, as an admin, force a confirmation.&lt;/li&gt;
&lt;li&gt;See the order edit merged into the order and continue managing your order as usual.&lt;/li&gt;
&lt;/ol&gt;


 


&lt;p&gt;Editing orders in Medusa allows you to update, add and remove line items on already placed orders with a few easy steps.&lt;/p&gt;

&lt;p&gt;Helpful timeline events ensure that you and your team have visibility to understand the order’s history and assist customers with questions about their orders.&lt;/p&gt;

&lt;p&gt;To learn more about how we designed Medusa’s order editing capabilities, check out &lt;a href="https://medusajs.com/blog/git-inspired-order-editing-feature/" rel="noopener noreferrer"&gt;this article that dives deeper into the solution&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Give your customers confidence and peace of mind
&lt;/h3&gt;

&lt;p&gt;The confirmation step is essential to the order editing flow and ensures that your customer is always in the loop about changes to their order. You can configure notifications that automatically inform customers that they must accept changes to their orders.&lt;/p&gt;

&lt;p&gt;They can confirm their changes with simple steps and pay for potential shipping and total differences.&lt;/p&gt;


 


&lt;p&gt;Customers can accept order edits to stay in the loop about changes to their order and pay for potential differences. &lt;/p&gt;

&lt;p&gt;For businesses, Medusa’s extensible API also provides a new opportunity to upsell customers and highlight products.&lt;/p&gt;

&lt;h2&gt;
  
  
  New possibilities with great APIs
&lt;/h2&gt;

&lt;p&gt;Everything we build at Medusa is deliberately designed to give a robust starting point while remaining open for customizations and extensions that can make Medusa truly your own. This is also the case for Order Editing and Payment Collections, where plugins and projects can now listen to a comprehensive set of new events that can act as triggers for automations and integrations.&lt;/p&gt;

&lt;p&gt;Accounting and fulfillment plugins, for example, will be able to listen for changes to orders and update systems to ensure consistency throughout your stack. This eliminates manual work while giving your customers a fast and smooth experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/subscribers/my-subscriber.ts&lt;/span&gt;

&lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OrderEditService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REQUESTED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&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;analyticsData&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;getTrackingData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;analyticsProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Order Edit Requested&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;analyticsData&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, our Payment Collection API will soon expand to support broader use cases like re-authorizing expired payment authorizations and creating new payment experiences for your customers, like installments or deposit payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out now
&lt;/h2&gt;

&lt;p&gt;We are excited to see what unique new experiences and plugins will be built with the new Order Editing and Payment Collection capabilities.&lt;/p&gt;

&lt;p&gt;Try it out now by upgrading to the latest version of Medusa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @medusajs/medusa@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or start a new project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create medusa-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’re interested in learning more about this feature, check out &lt;a href="https://medusajs.com/blog/git-inspired-order-editing-feature/" rel="noopener noreferrer"&gt;this article which goes over how Git and GitHub inspired the design of the Order Editing capabilities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also learn more about order editing in our documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.medusajs.com/advanced/admin/order-edit" rel="noopener noreferrer"&gt;How to implement order editing functionalities for administrators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.medusajs.com/advanced/storefront/handle-order-edits" rel="noopener noreferrer"&gt;How to implement order editing functionalities in a storefront&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OrderEdit &lt;a href="https://docs.medusajs.com/api/admin/#tag/OrderEdit" rel="noopener noreferrer"&gt;Admin&lt;/a&gt; and &lt;a href="https://docs.medusajs.com/api/store/#tag/OrderEdit" rel="noopener noreferrer"&gt;Store&lt;/a&gt; API reference&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tooling</category>
      <category>developers</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Introducing Medusa.express: The easiest way to set up an ecommerce store</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Mon, 14 Feb 2022 18:18:37 +0000</pubDate>
      <link>https://forem.com/medusajs/introducing-medusaexpress-the-easiest-way-to-setup-an-ecommerce-store-ig2</link>
      <guid>https://forem.com/medusajs/introducing-medusaexpress-the-easiest-way-to-setup-an-ecommerce-store-ig2</guid>
      <description>&lt;p&gt;&lt;a href="http://medusa.express" rel="noopener noreferrer"&gt;medusa.express&lt;/a&gt; automatically creates pages for the products in your catalog, each of them optimized to make the purchasing experience as frictionless as possible, by bundling the checkout flow alongside the product. &lt;/p&gt;

&lt;p&gt;The underlying thesis is that not all customers and not all products benefit from a fully-fledged website experience. Consider three different examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retargeting:&lt;/strong&gt; Customers in the lowest part of the marketing funnel, say customers who have already shown an interest for a given product. Why not give these customers a super quick way to purchase your products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex purchases:&lt;/strong&gt; Consider stores with huge product catalogs where customers need guidance (algorithmic or human powered) to find the desired product - once the right product has been determined the goal becomes to complete the transaction both from the store’s and the customer’s perspective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple store owners:&lt;/strong&gt; Some people simply do not need a full store setup to sell their products; bloggers, one-item sellers or outbound salespeople might be sufficiently served by sending a link to their users for purchasing certain products while not having to maintain a full webshop frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A customer visits a link e.g. &lt;a href="https://medusa.express/basic-tshirt" rel="noopener noreferrer"&gt;https://medusa.express/basic-tshirt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The customer selects the variant of the product they wish to buy&lt;/li&gt;
&lt;li&gt;Customer completes the checkout flow and the order is completed&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h2&gt;
  
  
  Why did we build this concept?
&lt;/h2&gt;

&lt;p&gt;One of the most liberating things about using a headless commerce engine, like Medusa, is that you are not constrained to follow any standards around how the shopping experience should be for your products. Standards are great in most cases, but they do also sometimes limit what is possible and therefore hinders you from opportunities if the standards are too tightly coupled with your software. &lt;/p&gt;

&lt;p&gt;You are surely familiar with the standard 2-column product grid, that serves as entry points to a product page with an image on the left side a size and quantity ticker on the right side, which ultimately have the goal of taking you to a checkout flow that looks the same across all brands. This experience has not changed much over the past many years and a likely reason for this is that companies still treat their digital commerce channels as physical retail stores.&lt;/p&gt;

&lt;p&gt;In physical retail stores, all customers are directed through the same path and products are placed along this path in a way that is meant to optimize the stores sales as carefully planned by a retail merchandiser. The same approach has been taken for digital commerce experiences to the point where even the most advanced personalization engines for ecommerce rarely offer solutions different from placing candy and chocolates along side each other in a supermarket does. &lt;/p&gt;

&lt;p&gt;What is yet to be leveraged is the fact that the internet enables an infinite amount of purchasing experiences to be offered and &lt;a href="http://medusa.express" rel="noopener noreferrer"&gt;medusa.express&lt;/a&gt; is a simplified demo that illustrates how headless commerce can make this possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it now!
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;: To use Medusa Express, you are required to have a running Medusa server. Check out &lt;a href="https://docs.medusajs.com/quickstart/quick-start/" rel="noopener noreferrer"&gt;our quickstart guide&lt;/a&gt; for a quick setup. &lt;/p&gt;

&lt;p&gt;1. Create your Medusa Express project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 https://github.com/medusajs/medusa-express-nextjs medusa-express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Navigate to your project and install dependencies&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;medusa-express

yarn
&lt;span class="c"&gt;# or &lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. Link your Medusa server&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.env.local&lt;/code&gt; file and add the following environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NEXT_PUBLIC_MEDUSA_BACKEND_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:9000
&lt;span class="c"&gt;# Stripe key is required for completing orders&lt;/span&gt;
&lt;span class="nv"&gt;NEXT_PUBLIC_STRIPE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pk_test_... 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Try it out!&lt;/p&gt;

&lt;p&gt;Spin up your Medusa server and Medusa Express project and try it out. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ensure that your &lt;code&gt;STORE_CORS&lt;/code&gt; configuration in &lt;code&gt;medusa-config.js&lt;/code&gt; in your Medusa project includes the URL of your Medusa Express project (defaults to &lt;code&gt;http://localhost:8000&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;&lt;a href="http://Medusa.Express" rel="noopener noreferrer"&gt;Medusa.Express&lt;/a&gt; is a solution for big and small stores alike. Whether you want an easy way for your customers to buy your store’s products, or you want a simple way to let people buy your products online, Medusa.Express is a fast way that lets you deliver a quick shopping experience for your users.&lt;/p&gt;

&lt;p&gt;Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via &lt;a href="https://discord.gg/F87eGuwkTp" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Wrapping up 2021 and what's in store for 2022</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Tue, 04 Jan 2022 12:57:31 +0000</pubDate>
      <link>https://forem.com/medusajs/wrapping-up-2021-and-whats-in-store-for-2022-22i5</link>
      <guid>https://forem.com/medusajs/wrapping-up-2021-and-whats-in-store-for-2022-22i5</guid>
      <description>&lt;p&gt;2021 has been quite overwhelming with all the interest we have received from the developer community - thanks a ton to all of you for that! Read below for a quick review of 2021 and a short glimpse into 2022.&lt;/p&gt;

&lt;h2&gt;
  
  
  Highlights of 2021 ⚡️
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Product highlights:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fully open-source; from core and admin to starters and design system&lt;/li&gt;
&lt;li&gt;Created an onboarding flow that gets you up and running with only 2 commands&lt;/li&gt;
&lt;li&gt;Developing starters that allow you to spin up a full store with a Gatsby or Next.js frontend&lt;/li&gt;
&lt;li&gt;Building an extensive plugin library with integrations to Stripe, Contentful, Strapi, Algolia, Paypal, and many more&lt;/li&gt;
&lt;li&gt;Building out our documentation, incl. deployment guides, setup tutorials, plugin guides&lt;/li&gt;
&lt;li&gt;Migrating the project to Typescript&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Community highlights:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Growing from 10 to +5,500 GitHub stars since our official launch 6 months ago&lt;/li&gt;
&lt;li&gt;Launching our community Discord and welcomed ~700 amazing members&lt;/li&gt;
&lt;li&gt;Hosting our first Hackathon and got 85 contributions to the core project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is to come in 2022 🔮
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Medusa 2.0
&lt;/h3&gt;

&lt;p&gt;We aim to launch Medusa 2.0 by the end of Q1. A first core foundation for this will be released with our new &lt;a href="https://www.medusajs.com/post/admin-redesign"&gt;Admin dashboard in January&lt;/a&gt;. We will share more details about the roadmap as we progress on our &lt;a href="https://discord.gg/F87eGuwkTp"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doubling down on community
&lt;/h3&gt;

&lt;p&gt;Hackathon, conferences, merch store, video tutorials, and a heck of a lot more; community is king and we want to give back for all the contributions we have received through many more activities addressed specifically to our community.&lt;/p&gt;

&lt;h3&gt;
  
  
  Medusa Cloud
&lt;/h3&gt;

&lt;p&gt;It should not take much to deploy Medusa and although our product will always remain open-source and available for self-hosting, we want to make it a one-click experience to get started with a Medusa store - and don’t worry you will always own the code that powers your store so that when it is time to customize your can quickly take over.&lt;br&gt;
Plenty of exciting things to happen. We cannot wait to show it all to you!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>Why we migrated to TypeScript after all</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Fri, 19 Nov 2021 11:13:25 +0000</pubDate>
      <link>https://forem.com/medusajs/why-we-migrated-to-typescript-after-all-3efa</link>
      <guid>https://forem.com/medusajs/why-we-migrated-to-typescript-after-all-3efa</guid>
      <description>&lt;p&gt;Over the past two weeks, we have been working hard on migrating parts of &lt;a href="https://www.medusajs.com/" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt;'s codebase to TypeScript. We are doing this for a couple of reasons all of which have to do with improving the developer experience. Now that we are slowly merging the first parts of this migration we would love to share some insights into why we didn't just go with TypeScript in the first place and why we have decided that the time was right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  A bit of background
&lt;/h3&gt;

&lt;p&gt;Before working full-time on Medusa we did agency work for a handful of different e-commerce clients; one of them wanted to migrate away from their solution back then and when we couldn't find a new suitable platform for them we agreed to build a custom solution from scratch. We didn't intend for this to be anything other than a solution that would optimize and automate all the workflows for the client so we went straight into development mode. As the project grew and our client grew, new requirements emerged and we had to go back to the drawing board. This was the first time that we had to weigh our options in the context of the project being maintainable and easy to use in the long term. We evaluated approaches based on a couple of insights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Growing businesses change (a lot)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;When you are on a growth path you can get by with basic tools in the beginning, but when processes get more complex you will typically need to adapt the tools in your stack. We wanted to build our architecture to accommodate this insight; making it easy to compose your e-commerce stack with the best-in-breed tools. &lt;em&gt;Note&lt;/em&gt;&lt;em&gt;:&lt;/em&gt;* best-in-breed for a small 2 person team is not the same as best-in-breed for a 25 person team, so your stack has to be dynamic.*&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Growing businesses need control&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;If you are growing rapidly you need to be in control of what happens when; you can't make a feature request and then sit around for 2 years waiting for it to be made available. You must be able to get the process moving straight away.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Customer Experience is king&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;If you are trying to stand out in the crowd of standardized websites you need to have a razor-sharp focus on the customer experience. Customers should feel that they are entering into an on-brand universe when visiting an e-commerce site and to do so you may have to change up the standard browsing, purchasing, and checkout process to truly accommodate the product or brand.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Developers are the key to making all of the above succeed&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Developers get a bad rap for being expensive, slow, and complicated to work with and this makes sense to a certain extent; however, there is an incredible amount of projects - Medusa being one of them - being built that focus on making developers more efficient, making the requirements for developer capabilities lower and ultimately making it easier for merchants to start leveraging headless technologies earlier than they otherwise would have. As the ecosystem of great developer tools grow it will become increasingly appealing to new merchants to make the switch as early as possible.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The above requirements were foundational for some of the high-level decisions we took in the early days:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We created a simple and modular architecture that gives you all the basics out of the box and provides the interfaces necessary to easily integrate with other tools to give you the optimal stack.&lt;/li&gt;
&lt;li&gt;We open-sourced everything to give users and merchants full control of what gets built&lt;/li&gt;
&lt;li&gt;We provide a great APIs and tooling to create custom e-commerce experiences that move the needle&lt;/li&gt;
&lt;li&gt;We think carefully about how to design things in the most developer-friendly way and &lt;strong&gt;choose tools in our stack that developers like&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why we didn't go with TypeScript in the first place
&lt;/h3&gt;

&lt;p&gt;You may be thinking now: okay but why on earth didn't you start with TypeScript if you wanted to use tools that developers like. The truth is that when we started developing we were optimizing for speed; with limited TypeScript experience back then we felt that there would potentially be many unknown issues to overcome if we had decided to go down that route. Furthermore, TypeScript was not as widely established as it is today and it would have been really sad if we had chosen a language that would wane in popularity after a couple of years &lt;a href="https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS" rel="noopener noreferrer"&gt;(&lt;strong&gt;cough&lt;/strong&gt; CoffeeScript and friends)&lt;/a&gt;. That isn't to say that we didn't like TypeScript, on the contrary, we have always thought TypeScript was incredibly powerful, however, &lt;a href="https://en.wikipedia.org/wiki/Lindy_effect" rel="noopener noreferrer"&gt;by Lindy&lt;/a&gt;, we decided that it was worth waiting a bit.&lt;/p&gt;

&lt;p&gt;Not going with TypeScript in the first place allowed us to do other amazing things like creating an incredibly powerful core architecture and a handful of great plugins that provided integrations for systems like &lt;a href="https://github.com/medusajs/medusa/tree/master/packages" rel="noopener noreferrer"&gt;Stripe, Segment, Sendgrid, and others&lt;/a&gt;. We were able to do this at an incredible pace and iterate quickly, changing APIs on a day to day basis (one of the privileges of the early days with few customers) without too many complications which eventually got us to this point in time, where we have figured out the ins-and-outs of how data should be flowing and where we have a good understanding of the &lt;strong&gt;types and interfaces&lt;/strong&gt; needed for modularity and composability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is the time right now
&lt;/h3&gt;

&lt;p&gt;When you are the lone developer and user of a project you know the APIs intimately because you have written them. Sometimes your memory might be a bit unclear, but generally, it is not a distractor for your workflow, so you don't think that much about IntelliSense not kicking in. That changes a lot, however, when you start working on things that are not your creation and not only from the user perspective also from the contributor perspective. Say you are working on code that calls some function that someone else from your team wrote. While you might have been in talks with your teammate about how the APIs are meant to work and what kind of dataflows should be going on, you might not know the exact implementation details, and that is when it starts being super useful to have all of your tooling in place. &lt;/p&gt;

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

&lt;p&gt;We are so happy to see the community around Medusa growing and we are therefore very much aware that we need to improve all the tools in the ecosystem as much as possible to allow for the best developer experience. Furthermore, we feel confident that TypeScript will be around for a long time, and for this reason, now is the time! &lt;/p&gt;

&lt;h3&gt;
  
  
  How we are going about it
&lt;/h3&gt;

&lt;p&gt;Migrating our entire codebase will take some time so we are starting with the parts that make the most sense and moving along. We are already using TypeORM for the data layer so all of our database entities are typed. We have gone ahead and added TypeScript to all of the core API's controllers typing all payloads and responses. This adds the amazing benefit of being able to share the type definitions between the core project and our JS client essentially ensuring that when you are making calls through our client library &lt;strong&gt;you are seeing the exact class properties that are being used to validate the request payload&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What's next&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We are going to add TypeScript throughout the core package as we go along and our goal is to migrate all of our code eventually. If you are interested in helping out or want to try Medusa go check out our &lt;a href="https://github.com/medusajs/medusa" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; and &lt;a href="https://discord.gg/xpCwq3Kfn8" rel="noopener noreferrer"&gt;join our Discord&lt;/a&gt; where you can get direct access to the engineering team!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>An open-source implementation of idempotency keys in NodeJS with Express</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Thu, 21 Oct 2021 14:59:44 +0000</pubDate>
      <link>https://forem.com/medusajs/an-open-source-implementation-of-idempotency-keys-in-nodejs-with-express-2093</link>
      <guid>https://forem.com/medusajs/an-open-source-implementation-of-idempotency-keys-in-nodejs-with-express-2093</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This post covers how idempotency keys are used in &lt;a href="https://www.medusajs.com/" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; and how you can implement your own idempotency key logic in a NodeJS application to make your API more robust. This post and the implementation discussed here are inspired by &lt;a href="https://brandur.org/idempotency-keys" rel="noopener noreferrer"&gt;this article&lt;/a&gt; by Brandur.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is idempotency?
&lt;/h2&gt;

&lt;p&gt;Idempotence is a mathematical term used to describe algebraic expressions that remain invariant when raised to a natural power - the word itself comes from the Latin words &lt;em&gt;idem&lt;/em&gt; and &lt;em&gt;potence&lt;/em&gt; meaning &lt;em&gt;same&lt;/em&gt; and &lt;em&gt;power&lt;/em&gt; respectively. In software and tech idempotency typically refers to the idea that you can perform an operation multiple times without triggering any side effects more than once. This is an extremely powerful property for fault tolerance in larger systems where service availability cannot be guaranteed. If you are familiar with RESTful design you have probably heard that &lt;code&gt;DELETE&lt;/code&gt; requests should be idempotent, meaning that no matter how many times you make a &lt;code&gt;DELETE&lt;/code&gt; request on a certain resource it should always respond with confirmation that the resource has been deleted (unless business rules don't allow it that is). &lt;/p&gt;

&lt;p&gt;In fintech applications, idempotency is typically extended to other types of requests to ensure that sensitive operations like issuing money transfers, etc. don't erroneously get duplicated. For example, Stripe has support for idempotency on all of their requests controlled by an &lt;code&gt;Idempotency-Key&lt;/code&gt; header. This allows you to safely retry requests if necessary, for example, you may be issuing an "Authorize Payment", but just after the request is sent your internet experiences an outage and you have no way of knowing whether the payment was successfully authorized or not; however, by using idempotency keys you can safely retry the "Authorize Payment" request without having to worry about making two payment authorizations.&lt;/p&gt;

&lt;p&gt;One of the major benefits of headless commerce is that you can pick and choose the tools in your stack and have them integrate with each other for a best in breed stack; however, the more systems that are connected the more prone you will be to having inconsistencies across your tools, e.g. because of things out of your control such as server outages, connectivity issues or other unexpected situations. To solve this issue Medusa implements idempotency key support so that you can safely retry requests until consistency is confirmed. &lt;/p&gt;

&lt;h2&gt;
  
  
  How can idempotency keys be used?
&lt;/h2&gt;

&lt;p&gt;There are two perspectives worth considering when answering the question of how idempotency keys can be used: one is from a client perspective, for example, when calling an API from a frontend, the other is from a server perspective when transferring data between systems. The purpose is the same in both circumstances, namely to ensure that an operation is completed correctly. &lt;/p&gt;

&lt;h3&gt;
  
  
  Client perspective
&lt;/h3&gt;

&lt;p&gt;Imagine that you are adding a line item to a shopping cart through an API like Medusa's. You make a request to add the line item, but right after sending the request your internet drops resulting in a "Server not reachable" response - at this time it is not clear whether the request made it to the server and that the underlying database was able to successfully update your cart with the new item or if the internet dropped before sending the request and thus didn't result in an update in the backend. In the former case, a retry would result in your cart now having two items instead of one as expected, so if you retry the request you will have to have a compensating mechanism, which is tricky and tedious to build and test. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;A typical retry flow&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrrg7wu30bpkdekitn66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrrg7wu30bpkdekitn66.png" alt="Retry Flow for failed API requests"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This is where idempotency keys come in handy as they can help you ensure that the intended state is reached even in fragile environments. In practice the requests would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&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;makeRequest&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/store/carts/[cart-id]/items&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;variant_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;shouldRetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shouldRetry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;shouldRetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shouldRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// retry logic&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shouldRetry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// expontential back-off&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;shouldRetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&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;Notice that it is the same idempotency key that is being passed across all retries. This indicates to the backend: "Hey, I am only interested in this operation happening once - can you please check if the operation has already succeeded. If so just respond with the result of the succeeded operation, otherwise, perform the operation now and store the result under this key so subsequent requests with the same key don't perform the operation multiple times". &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Server perspective&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now, shifting to the server perspective, imagine that you are integrating your commerce API with a payment provider like Stripe and you need to allow your API's consumers to be able to issue refunds. You both have to keep track of the refunds in your system but will also have to call Stripe's API to make sure that the refund of the money goes through to the customer's bank account. Consider what steps your backend would have to take when handling a refund request - you may come up with something along the lines of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate that the requested amount can be refunded (i.e. less than the original payment for the order minus what has already been refunded).&lt;/li&gt;
&lt;li&gt;Create a record of the refund in your database.&lt;/li&gt;
&lt;li&gt;Issue refund through the Stripe API.&lt;/li&gt;
&lt;li&gt;Store Stripe refund id in the internal record of refund.&lt;/li&gt;
&lt;li&gt;Dispatch job to send a refund confirmation email to the customer&lt;/li&gt;
&lt;li&gt;Complete request and respond&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A naive implementation would just execute each of the steps and hope for the best, but that would be a bad idea. Consider what would happen in a case where a server experiences an outage and you have to decide whether to retry the request or not - you don't know which of the steps failed so it is unclear whether the Stripe request has been processed; if it has a new request would duplicate the refund which is obviously bad. Alternatively, if the Stripe request hasn't been processed you may have stale data in your internal database.&lt;/p&gt;

&lt;p&gt;A slightly better approach would be to wrap everything into an ACID transaction and roll back if something fails. This way you don't end up having records in your database if something fails unexpectedly; however, in the case of an error you are still left in the dark as to whether the Stripe request was successfully processed or not; so how might you safely retry your failed request? Luckily, Stripe has support for idempotency keys so if your implementation makes sure to forward the idempotency key to Stripe you can safely retry your request without having to worry about refunding the requested amount more than once; however, it is not all external systems that have support for idempotency keys and under such circumstances, you need to take additional measures for your requests to be idempotent. You will see how this can be accomplished through atomic phases shortly. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Idempotency Key implementation in Expressjs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The outline here will reveal how to implement idempotency keys in a NodeJS application with Express. It is assumed that the underlying database for the application is an ACID-compliant relational database like Postgresql. Before going further it should be clarified what exactly is meant by an &lt;em&gt;idempotency key&lt;/em&gt; in this context: an idempotency key is a string that identifies a database entity that tracks the progress of an API request. By tracking the progress, idempotency keys can either pick up where previously failed requests left off or if a previous request succeeded they can be used to return a cached result of the request. &lt;/p&gt;

&lt;p&gt;Building further on the idea of a Cart API, consider the API request needed to transform a Cart into an Order. The steps to take will be something like the following:&lt;/p&gt;

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

&lt;p&gt;Consider the steps in the above request and what your system state and your payment providers state will have recorded in each of the failure points. You may consider each of them and find the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure point #1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have created a record of the incoming request, but have failed to authorize the payment and no order has been created. You can safely retry the request. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure point #2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The payment has successfully been authorized and a record of the payment is stored. The order has not been created. If you retry the request now you will be authorizing the payment again - this may fail or worse authorize a new payment duplicating the payment from the previous request. Unless some compensation mechanism has been implemented in your authorization logic that checks for a previous payment it is generally not safe to retry the request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure point #3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this point you have both authorized the payment and created an order in your system. Retrying the request may result in both a duplicate order and a duplicate payment authorization. &lt;/p&gt;

&lt;p&gt;Now consider what will happen if you wrap your entire request in a transaction that rolls back after each of the failure points. For failure point 1 you can safely retry, but rolling back at failure point 2 and 3 will result in your own state and the external state of the payment provider being out of sync. Namely the payment provider will have a payment that your internal system has no record of. In order to overcome this problem you must be able to recover from failed requests depending on whether the external system mutation has been completed or not. In simple terms a request retry should be able to say: "If the payment was already authorized skip that step and continue with creating the order. If the payment wasn't authorized do that now and continue". The points in the request lifetime where you wish to be able to retry from will be called recovery points in the following discussion. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomic phases&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Between each recovery point you will complete an atomic phase, which is a set of operations that happen within a transaction. If one of the operations fail you will roll back the atomic phase and a retry of the request can then pick up from the recovery point that came before the atomic phase. Considering the request lifecycle above once again, you should realize that you will want 3 atomic phases. One before the payment authorization when the idempotency key is created, one containing the payment authorization and one after the payment authorization has been completed. The diagram below illustrates the atomic phases and each of the recovery points:&lt;/p&gt;

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

&lt;p&gt;Retrying failed requests will now pick up from the most recently reached recovery point meaning that new requests will either skip the payment authorization or retry it if it failed, but will never duplicate it.&lt;/p&gt;

&lt;p&gt;Now that you have a rough idea about the parts of the system that you will need to keep track of it is time to look at how you might implement this starting with a simplified database schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;IdempotencyKey&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;idempotency_key&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;request_path&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;request_params&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;response_code&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;response_body&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;recovery_point&lt;/span&gt;

&lt;span class="nx"&gt;Payment&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;payment_provider&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;idempotency_key&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;

&lt;span class="nx"&gt;Cart&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;completed_at&lt;/span&gt;

&lt;span class="nx"&gt;Order&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;payment_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the idempotency key entity notes which path and which parameters an API call is requesting. It also has fields for the response code and body to send after the API call has succeeded so that retries of completed requests can skip directly to the response.&lt;/p&gt;

&lt;p&gt;To make atomic phases easy to work with consider the implementation below from Medusa's IdempotencyKeyService.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;workStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&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;let&lt;/span&gt; &lt;span class="nx"&gt;key&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;recovery_point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response_body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;manager&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recovery_point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;recovery_point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;recovery_point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;finished&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;response_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERIALIZABLE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/medusajs/medusa/blob/master/packages/medusa/src/services/idempotency-key.js" rel="noopener noreferrer"&gt;The &lt;code&gt;IdempotencyKeyService&lt;/code&gt; in Medusa&lt;/a&gt; allows you to execute an atomic phase by using the service method called &lt;code&gt;workStage&lt;/code&gt;, which takes an &lt;code&gt;idempotencyKey&lt;/code&gt; string and a &lt;code&gt;func&lt;/code&gt; function containing the operations to be executed inside the atomic phase. The function can return either a &lt;code&gt;recovery_point&lt;/code&gt; string in which case the idempotency key's recovery point is updated to that value or alternatively a &lt;code&gt;response_body&lt;/code&gt; and &lt;code&gt;response_code&lt;/code&gt; in which case it is assumed that the  operation is completed and we can allow the recovery point to be updated to "finished". &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API controller implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now it is time to implement the API controller that takes in the &lt;a href="https://github.com/medusajs/medusa/blob/master/packages/medusa/src/api/routes/store/carts/complete-cart.js" rel="noopener noreferrer"&gt;request to create an order&lt;/a&gt; from a cart. Below you are using a state machine pattern to step through each of the API request's atomic phases. &lt;/p&gt;

&lt;p&gt;Notice that the first step in the implementation is to upsert the idempotency key: either by using a provided token in the &lt;code&gt;Idempotency-Key&lt;/code&gt; header or alternatively by creating a new one at random (this happens in &lt;code&gt;initializeRequest&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Once the idempotency key is retrieved the request moves into the state machine where the recovery point of the idempotency key determines which atomic phase should be executed first. If the most recent recovery point is &lt;code&gt;"started"&lt;/code&gt; the request moves to authorization of the payment, if that has already been completed the request goes straight to creating the order. &lt;/p&gt;

&lt;p&gt;The code snippet below is a simplified version of Medusa's request handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKeyService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idempotencyKeyService&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;cartService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cartService&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;orderService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orderService&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;headerKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;idempotencyKey&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;idempotencyKeyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;headerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to create idempotency key&lt;/span&gt;&lt;span class="dl"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Expose-Headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;inProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recovery_point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKeyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;workStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&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;let&lt;/span&gt; &lt;span class="nx"&gt;cart&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;cartService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed_at&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="na"&gt;response_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MedusaError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CART_INCOMPATIBLE_STATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cart has already been completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MedusaError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOT_ALLOWED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nx"&gt;cart&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;cartService&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorizePayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency_key&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="na"&gt;recovery_point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payment_authorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;inProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payment_authorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKeyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;workStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&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;cart&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;cartService&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;total&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="na"&gt;relations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payment_sessions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&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;orderService&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFromCart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;response_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;inProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;finished&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;inProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;idempotencyKey&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;idempotencyKeyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;recovery_point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;finished&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;response_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown recovery point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response_code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response_body&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;Notice how unexpected errors are bubbled out to the application controller - it is assumed that your Express app has an error boundary, that handles the error properly. Expected errors that are definitive, that is no matter how many calls you make it should always result in the same error code, which can be stored in the idempotency key so that subsequent requests can short circuit and send the cached response directly.&lt;/p&gt;

&lt;p&gt;Using this pattern across your API endpoints will improve the robustness of your API by making it safe to retry all requests. This is useful for requests that modify the internal state alone, but the concept is especially powerful when dealing with requests that modify external states outside the control of your system. The key to making requests like these safe is to wrap external state modifications in atomic phases and allow retries to pick up both before or after such modifications, depending on the progress made from previous requests. &lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency in Medusa
&lt;/h2&gt;

&lt;p&gt;In Medusa idempotency has so far been implemented for a handful of API requests, and support is continually being added to more endpoints. The goal is to support idempotency keys for all state-mutating requests so that you can be certain that retrying your requests is safe and harmless. The next step for Medusa will be to add idempotency patterns into the plugin APIs so that Medusa's core can implement self-healing logic that identifies and resolves inconsistencies between systems in your e-commerce stack. This will be a major improvement for the developer experience related to building headless commerce solutions, where there are lots of moving parts and hence lots of potential points of failure. &lt;/p&gt;

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

&lt;p&gt;If you wish to dive deeper into how idempotency keys are implemented in Medusa visit the &lt;a href="https://github.com/medusajs/medusa" rel="noopener noreferrer"&gt;Medusa GitHub repository&lt;/a&gt;. You are also more than welcome to join the &lt;a href="https://discord.gg/xceUDkGUtH" rel="noopener noreferrer"&gt;Medusa Discord server,&lt;/a&gt; where you can get direct access to the Medusa engineering team, who will be happy to answer any questions you might have. &lt;/p&gt;

&lt;p&gt;Thanks for reading and if you haven't already go check out the post by Brandur that inspired the implementation of idempotency keys in Medusa. Brandur also has a number of other articles that are definitely worth reading if you are looking to improve the robustness of your APIs.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Making your store more powerful with Contentful</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Fri, 08 Oct 2021 16:19:28 +0000</pubDate>
      <link>https://forem.com/medusajs/making-your-store-more-powerful-with-contentful-3efk</link>
      <guid>https://forem.com/medusajs/making-your-store-more-powerful-with-contentful-3efk</guid>
      <description>&lt;p&gt;In &lt;a href="https://docs.medusajs.com/how-to/headless-ecommerce-store-with-gatsby-contentful-medusa/" rel="noopener noreferrer"&gt;part 1&lt;/a&gt; of this series you have set up &lt;a href="https://www.medusajs.com/" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; with Contentful as your CMS system and added a Gatsby storefront. In this part you will get a further introduction to Contentful and learn how &lt;a href="https://github.com/medusajs/medusa/tree/master/packages/medusa-plugin-contentful" rel="noopener noreferrer"&gt;&lt;code&gt;medusa-plugin-contentful&lt;/code&gt;&lt;/a&gt; can be leveraged to make your store more powerful. Apart from a front page, product pages and a checkout flow, most ecommerce stores also need miscalleneous pages like About and Contact pages. In this guide you will add a Rich Text content module to your Contentful space so that you can make this pages cool. You will also see how the content modules can be used to give your product pages more life.&lt;/p&gt;

&lt;p&gt;What you will do in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a rich text content module&lt;/li&gt;
&lt;li&gt;Add rich text to your &lt;code&gt;/about&lt;/code&gt; page&lt;/li&gt;
&lt;li&gt;Add a "Related Products" section to your product page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Topics covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contentful Migrations&lt;/li&gt;
&lt;li&gt;Product enrichment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a rich text content module
&lt;/h2&gt;

&lt;p&gt;In this guide you will make use of &lt;a href="https://github.com/contentful/contentful-migration" rel="noopener noreferrer"&gt;Contentful Migrations&lt;/a&gt; to keep a versioned controlled record of how your Content evolves over time. The Contentful app allows you to create content models straight from their dashboard, however, when using the migrations tool you will be able to 1) quickly replicate your Contentful space and 2) incorporate migrations as part of a CI/CD pipeline. &lt;a href="https://www.contentful.com/help/cms-as-code/" rel="noopener noreferrer"&gt;You can read more about how to use CMS as Code here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To prepare your migration create a new file at &lt;code&gt;contentful-migrations/rich-text.js&lt;/code&gt; and add the following code:&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="c1"&gt;// contentful-migrations/rich-text.js&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;richText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;richText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rich Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;richText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title (Internal)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Symbol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;richText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RichText&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;This small snippet will create a content model in your Contentful space with two fields: a title which will be used to name entries in a meaningful manner (i.e. it won't be displayed to customers) and a body which contains the rich text to display. To apply your migration run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn migrate:contentful &lt;span class="nt"&gt;--file&lt;/span&gt; contentful-migrations/rich-text.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you go to your Contentful space and click Content Model you will see that the Rich Text model has been added to your space: &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FsCMjr4B.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FsCMjr4B.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The validation rules in the Page model only allow Hero and Tile Sections to be added to the Content Modules fields so you will need another migration to make it possible for pages to make use of the new Rich Text modules. Create a new migration at &lt;code&gt;contentful-migrations/update-page-module-validation.js&lt;/code&gt; and add the following:&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="c1"&gt;// contentful-migrations/update-page-module-validation.js&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contentModules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;linkType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;validations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;linkContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hero&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tileSection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;richText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After migrating your space you are ready create your new contact page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn migrate:contentful &lt;span class="nt"&gt;--file&lt;/span&gt; contentful-migrations/update-page-module-validation.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding Rich Text to About
&lt;/h2&gt;

&lt;p&gt;To use your new Rich Text module &lt;strong&gt;Content &amp;gt; Page &amp;gt; About&lt;/strong&gt;, and click &lt;strong&gt;Add Content &amp;gt; Page&lt;/strong&gt;. You will now make use of the new Rich Text module to add some more details about your store. You can write your own text or use the text provided below if you just want to copy/paste.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  About Medusa
&lt;/h3&gt;

&lt;p&gt;Medusa is an open-source headless commerce engine for fast-growing businesses. Getting started with Medusa is very easy and you will be able to start selling online with a basic setup in no time, however, the real power of Medusa starts showing up when you add custom functionality and extend your core to fit your needs.&lt;/p&gt;

&lt;p&gt;The core Medusa package and all the official Medusa plugins ship as individual NPM packages that you install into a Node project. You store and plugins are configured in your medusa-config.js file making it very easy to manage your store as your business grows. Custom functionality doesn't have to come from plugins, you can also add project-level functionality by simply adding files in your &lt;code&gt;src/&lt;/code&gt; folder. Medusa will automatically register your custom functionalities in the bootstrap phase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FhqiaoFq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FhqiaoFq.png" alt="Contentful Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have added your text you can click &lt;strong&gt;Publish changes&lt;/strong&gt; (make sure the About page is published too).&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the storefront to support the Rich Text module
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to jump straight to the final frontend code visit &lt;a href="https://github.com/medusajs/medusa-contentful-storefront/tree/part-2" rel="noopener noreferrer"&gt;medusajs/medusa-contentful-storefront@part-2&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To display your newly created Rich Text module open up the storefront code and create a new file at &lt;code&gt;src/components/rich-text/rich-text.jsx&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/rich-text/rich-text.jsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&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;renderRichText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gatsby-source-contentful/rich-text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../styles/rich-text.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RichText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;renderRichText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;renderRichText&lt;/code&gt; function is imported from the &lt;code&gt;gatsby-source-contentful&lt;/code&gt; plugin to easily transform the text you entered in the Rich Text module to html. To make the Rich Text component render nicely add a style file as well at &lt;code&gt;src/styles/rich-text.module.css&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* src/styles/rich-text.module.css */&lt;/span&gt;

&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;870px&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;If you restart your storefront server now you will not be able to see your new Rich Text module just yet. The last step to making that happen will be to let the Page component know to render the new Rich Text component when it encounters Rich Text in the Page's Content Modules. In your editor open up the file &lt;code&gt;src/pages/{ContentfulPage.slug}.js&lt;/code&gt; and add the following:&lt;/p&gt;

&lt;p&gt;At the top of the file import your &lt;code&gt;RichText&lt;/code&gt; component:&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="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;RichText&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/rich-text/rich-text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the &lt;code&gt;contentModules.map&lt;/code&gt; function return the &lt;code&gt;RichText&lt;/code&gt; component whenever a &lt;code&gt;ContentfulRichText&lt;/code&gt; module is encountered. Add a case to the switch statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ContentfulRichText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RichText&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally you will need to fetch the Rich Text data from Gatsby's data layer by modifying the GraphQL code at the bottom of the file after the line with &lt;code&gt;contentModules {&lt;/code&gt; add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ContentfulRichText&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;body&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="n"&gt;raw&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="n"&gt;internal&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="n"&gt;type&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;Restart your local Gatsby server and visit &lt;code&gt;http://localhost:8000/about&lt;/code&gt;, you will now see the your newly added Rich Text module.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F8Teuxin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F8Teuxin.png" alt="Rich Text Module"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enriching your Product pages
&lt;/h2&gt;

&lt;p&gt;You have now seen how the Page model in Contentful can be extended to include a new content module in a reusable and modular manner. The same idea can be extended to your Product pages allowing you to create completely bespoke universes around your products. You will use the same techniques as above to create a Related Products section below the "Medusa Shirt" product.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrating Products
&lt;/h3&gt;

&lt;p&gt;First, add a new field to the Product content model. Using migrations you can create a file &lt;code&gt;contentful-migrations/product-add-modules.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// contentful-migrations/product-add-modules.js&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;product&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contentModules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content Modules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;linkType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;validations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;linkContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hero&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tileSection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;richText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn migrate:contentful --file contentful-migrations/product-add-modules.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding "Related Products" Tile Section
&lt;/h3&gt;

&lt;p&gt;After the migration you can now add Content Modules to Products, to enrich the Product pages with relevant content. In this guide you will add a Tile Section that holds "Related Products", but the functionality could be further extended to showcase look book images, inspirational content or more detailed product descriptions.&lt;/p&gt;

&lt;p&gt;In Contentful go to &lt;strong&gt;Content &amp;gt; Product &amp;gt; Medusa Shirt&lt;/strong&gt; scroll all the way to the bottom, where you should be able to find the new &lt;em&gt;Content Modules&lt;/em&gt; field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FjUUpW9I.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FjUUpW9I.png" alt="Contentful Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Add content &amp;gt; Tile Section&lt;/strong&gt; which will open a new Tile Section. For the Title write "Related Products", and for Tiles click &lt;strong&gt;Add content &amp;gt; Add existing content &amp;gt; Medusa Waterbottle &amp;gt; Insert 1 entry&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FN7alMGz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FN7alMGz.png" alt="Contentful Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Publish&lt;/strong&gt; and make sure that the Medusa Shirt product is published too.&lt;/p&gt;

&lt;p&gt;Your data is now ready to be used in the storefront, but you still need to make a couple of changes to the storefront code to be able to view the new content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Content Modules to Product pages
&lt;/h2&gt;

&lt;p&gt;Just like you did for the Page component, you will have to fetch the Content Modules from Gatsby's GraphQL data layer. &lt;/p&gt;

&lt;p&gt;In the file &lt;code&gt;src/pages/products/{ContentfulProduct.handle}.js&lt;/code&gt; add the following in the GraphQL query at the bottom of the file (e.g. after the variants query):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# src/pages/products/{ContentfulProduct.handle}.js&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;contentModules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ContentfulTileSection&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;tiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ContentfulProduct&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;thumbnail&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="n"&gt;gatsbyImageData&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="n"&gt;internal&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="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ContentfulTile&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;cta&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;image&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="n"&gt;gatsbyImageData&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="n"&gt;link&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="n"&gt;linkTo&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;reference&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="n"&gt;slug&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;internal&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="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;internal&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="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet will query the Content Modules defined for the product and will allow you to use the data in your components. &lt;/p&gt;

&lt;p&gt;Next open up the &lt;code&gt;src/views/products.jsx&lt;/code&gt; file and add the following snippets. &lt;/p&gt;

&lt;p&gt;Import the &lt;code&gt;TileSection&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TileSection&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/tile-section/tile-section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the Content Modules in the JSX just before the final closing &lt;code&gt;div&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// src/views/products.jsx&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentModules&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentModules&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ContentfulTileSection&lt;/span&gt;&lt;span class="dl"&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TileSection&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the Gatsby server and visit &lt;a href="http://localhost:8000/product/medusa-shirt" rel="noopener noreferrer"&gt;http://localhost:8000/product/medusa-shirt&lt;/a&gt; you should now see the new "Related Products" Tile Section below the Product page controls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FAQHKA6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FAQHKA6j.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this guide you created a new content model for Rich Text input in Contentful using &lt;a href="https://github.com/contentful/contentful-migration" rel="noopener noreferrer"&gt;contentful-migration&lt;/a&gt;. You further extended the storefront to render the new Rich Text plugin. The concepts in this guide are meant to demonstrate how Contentful can be used to make your store more powerful in a modular and scalable way. The content modules covered in this guide could be further extended to add other custom modules, for example, you could add a Newsletter Signup, module that when encountered in the code renders a newsletter form.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;In the next part of this guide you will learn how to implement further commerce functionalities to your site such as adding support for discount codes, region based shopping and more. (Coming soon)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.medusajs.com/how-to/deploying-on-heroku/" rel="noopener noreferrer"&gt;Deploying Medusa on Heroku&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.medusajs.com/how-to/deploying-admin-on-netlify/" rel="noopener noreferrer"&gt;Deploying Medusa Admin on Netlify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>headless</category>
    </item>
    <item>
      <title>Introducing Medusa: Your building blocks for amazing digital commerce experiences</title>
      <dc:creator>Sebastian Rindom</dc:creator>
      <pubDate>Mon, 06 Sep 2021 08:02:18 +0000</pubDate>
      <link>https://forem.com/medusajs/introducing-medusa-your-building-blocks-for-amazing-digital-commerce-experiences-4nhp</link>
      <guid>https://forem.com/medusajs/introducing-medusa-your-building-blocks-for-amazing-digital-commerce-experiences-4nhp</guid>
      <description>&lt;p&gt;Over the past couple of years we have developed Medusa in close collaboration with customers that needed a headless solution that could support their growth for years to come. We are so excited to finally open up about what it is we have built, why we have built it, and how you can succeed with Medusa. Medusa is an open-source headless commerce engine that can power amazing digital commerce experiences and it is a great choice for any new headless ecommerce project out there.&lt;/p&gt;

&lt;p&gt;Headless ecommere has gained plenty of interest recently and more and more businesses are looking towards making the shift to a headless solution to leverage a more modular setup and all the benefits it brings, such as: better page performance, greater flexibility, better support for unique customer experiences, support for best-in-breed tech stacks, etc. When we first started working on Medusa we had no idea what headless ecommerce meant, and initially, we set out to build a fully-fledged platform, but more on this later!&lt;/p&gt;

&lt;p&gt;This is the first blog post we are publishing and it will stand as an important milestone for our work going forward. We have found that through modularity and simplicity our merchants end up being able to do much more with much less, which is why we will keep pursuing this approach when building out our product further. We want to provide a foundational set of building blocks that can be used to create unique, interesting, and powerful digital commerce experiences and we are so excited to see what amazing work we can help create.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why we built Medusa
&lt;/h3&gt;

&lt;p&gt;We didn’t wake up one morning thinking to ourselves that we wanted to build a headless commerce engine, but rather we got to build Medusa through experience with the limitations of existing platforms. Before transitioning to working full-time on Medusa, we operated as an agency, building all kinds of tools for our customers. We built everything from portfolio websites to custom integrations to ecommerce websites, but quickly started specializing in the latter; as we found new customers we also experienced that many of them were hitting limitations with the platforms available at the time.&lt;/p&gt;

&lt;p&gt;This became evident when a client of ours expressed interest in switching platforms. We started looking for good alternatives but it quickly became clear that none of them would support the expansion across markets and brand ownership that they required so we decided with them that we should try to build a bespoke solution that would cater to their exact needs and so we began building a fully-fledged ecommerce platform with a CMS system, fulfilment system, etc. It was not soon after that we realized that we couldn’t do everything well at once so we started removing parts of what we’d built and replacing them with integrations to better tools. In the end, we were left with a core that was capable of processing and managing orders, ensuring payments were going through, and handling basic product and customer data. What was even more important was that this core was extremely good at integrating other tools and as such able to function as the glue between all the tools and services in your ecommerce stack. Essentially we had built a headless ecommerce engine, simply by following what was easiest for us as developers and most cost-efficient and scalable for our customers.&lt;/p&gt;

&lt;p&gt;Our customers were really happy with the solution as they were able to expand to multiple markets while picking and choosing from any services they found interesting without having to worry about the cost of integration. Furthermore, our customers got a high degree of ownership when using our custom-built solution, as they could ask us to build features that they needed and have them released typically within a couple of days. When contemplating how we could ensure the same level of ownership going forward it became clear that we had to open-source the solution so that no central organization would ever be in control of what was possible for the merchant. Open-sourcing Medusa would also bring a bunch of other benefits such as leveraging the community for feedback and contributions which further strengthens the product.&lt;/p&gt;

&lt;p&gt;After experiencing the issues with the existing platforms we were confident that our product would be a good alternative for many new ecommerce projects and shortly after we decided to open-source the product we started pulling out the generic parts of the implementations we had done, gave it the name Medusa and made it available through GitHub. &lt;a href="https://github.com/medusajs/medusa"&gt;Go to Github to view the open source project&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Headless: The next generation of commerce
&lt;/h3&gt;

&lt;p&gt;In the early days of the internet operating ecommerce required big budgets and with customers still being new to the digital purchasing experience, only a small group of large enterprises set up online commerce channels by either building them from scratch or using solutions like Hybris. As more customers moved online, new platforms as Magento, Shopify, and WooCommerce emerged — these were easy to use and enabled merchants to make digital commerce available as a secondary sales channel.&lt;/p&gt;

&lt;p&gt;In recent years, ecommerce has become a primary channel for many businesses leading to fierce competition in the space, increasing the need for custom solutions to give unique buying experiences. In the quest for customization, many businesses have been left frustrated with the lack of flexibility that existing platforms provide. While optimizing for ease of use, most traditional platforms is putting their focus on templated “out-of-the-box” solutions neglecting the constraints this inevitably put on the development flexibility. In many cases, businesses have been forced to converge towards compromised solutions, or even make entire platform shifts, as their needs grow beyond what a templated solution can offer. Simultaneously, a much wider variety of businesses (e.g. service providers, B2B companies etc.) requiring fully custom solutions from the beginning have started moving online.&lt;/p&gt;

&lt;p&gt;The increasing demand for customization and flexibility has spurred an interest in headless commerce among performance-focused merchants that want differentiated and fully optimized customer experiences. Yet, many of them are still to make the move. One of the main reasons, why headless has yet to become the dominant type of ecommerce is that it can be expensive to operate a headless ecommerce stack as you need developers that can maintain the infrastructure that powers the integrations between systems in your stack and a high up-front investment for making the initial setup. This is something we are solving with Medusa by providing infrastructure that is preconfigured and optimized to run your projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting the developer first in your commerce setup
&lt;/h3&gt;

&lt;p&gt;The modularity and ownership that Medusa can offer is capable of powering incredibly diverse applications ranging from ecommerce websites to more niche cases like in-restaurant experiences and will with high probability be suitable for whatever digital commerce experience you can think of. Many businesses postpone the transition to headless as it can seem daunting and complex when you need developers to maintain your infrastructure and operations; we will make it possible to take out these concerns completely by providing the infrastructure that is configured and optimized to run Medusa projects, thereby making it much cheaper and more accessible for businesses to make the transition to headless early on.&lt;/p&gt;

&lt;p&gt;In more general terms we will be focusing on enabling the developer to do much more with less; this is in line with making sure that merchants can take ownership of their commerce setup as developers can help steer the direction and roadmap for your project without relying on us to create the features you need. By creating powerful tools for the developer we enable them to be more efficient and focused in their work. Examples of tools that we are building are; an infrastructure platform so developers don’t have to configure servers and databases, a powerful CLI that automates the redundant tasks developers face, a React component library to make it easy to build storefronts for Medusa, rich documentation that makes it easy to find what you are looking for and much more.&lt;/p&gt;

&lt;p&gt;With the core project being open-source we will also be open for contributions from the community making sure that we always have a product that is loved by developers so that they can do more!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What’s next?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We are working hard on completing our infrastructure product so that projects can easily be deployed to the cloud. In the meantime, if you are a developer we encourage you to try out Medusa, you can get up and running within 5 minutes and get a quick overview of what features our platform will bring. &lt;a href="https://docs.medusa-commerce.com/tutorial/set-up-your-development-environment"&gt;Check out the tutorial to get started now&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are a merchant looking for a new solution we would love to get in touch and discuss what challenges we can help solve and guide you through our process.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
