Skip to content

v2.6.0

Latest
Compare
Choose a tag to compare
@olivermrbl olivermrbl released this 04 Mar 13:22
· 8 commits to develop since this release

Highlights

Authentication improvements

This release enhances security around resetting passwords.

Updated token handling in password resets

Warning

Breaking changes: this change is not backward compatible due to security considerations

The token for updating provider identity is now accepted as a Bearer token in the authorization header instead of query parameters.

Before:

POST /auth/user/emailpass/update?token=ey...

After:

POST /auth/user/emailpass/update
// headers
{ authorization: "Bearer ey..." }

This enhances security around token management, for example, by preventing token exposure in logs.

More specifically, this will affect the reset password flow that relies on this endpoint.

Required action

  • In your client (e.g. Next.js storefront), move the token from query params to the authorization header

Removed deprecated actorType from reset password event
We have removed a long deprecated property, actorType from the reset password event. Instead, use the actor_type.

API Routes loader refactor

Important

While these changes sound dramatic, if you have used middleware and API Routes as intended, there will be no required actions nor breaking changes.

This release includes a refactor of our API Routes loading mechanism. The previous loader had long-standing issues that required hacky workarounds. To resolve these issues, we’ve rebuilt the loader from scratch to ensure predictable behavior in how routes and middleware are registered within the application.

The root of the issues with the old implementation was mainly related to the following decisions:

  • Plugins, Medusa core, and the application used isolated router instances of Express. This means global middleware and error handlers are isolated, too. If you define an error handler in your app, it won't be able to handle errors from the core routes.

  • Middlewares defined inside the src/api/middlewares.ts file were always defined before the filesystem routes, and this led to unexpected behavior in many scenarios. For example, the middleware handler registered for route admin/payments/payment-providers will also run when a request comes in for /admin/payments/:id, even though there is a static route in place for the /admin/payments/payment-providers URI.

  • There was no clarity on the ordering in which routes and middleware were executed. With the new implementation, this has been fixed (more on ordering is discussed below).

  • Overriding an existing route defines multiple express handlers under the hood. It worked previously, because the route handlers never call next, hence the previous implementation was never executed. With the newer implementation, the route overrides from a Medusa application will be registered over the routes from the core or the plugins.

To resolve these issues, we’ve rebuilt the loader from scratch to ensure predictable behavior in how routes and middleware are registered within the application. Additionally, the old API Routes loader unintentionally allowed overriding core API routes and middleware. While this behavior remains for now, it will be removed in a future release. Please prepare your application based on the behavior described in the following sections.

Global middleware

A global middleware is defined using the defineMiddlewares function without specifying an HTTP method.

defineMiddlewares([
  {
    matcher: '/admin',
    middleware: [
      (req, res, next) => console.log("Hi from global middleware")
    ],
  }
])

The order of registering global middleware is as follows:

  1. Global middleware from the core
  2. Global middleware from plugins (in the order plugins are registered)
  3. Global middleware from the app

Route middleware

A route middleware is also defined using the defineMiddlewares method. However, they must always specify the HTTP method(s) they want to target.

defineMiddlewares([
  {
    matcher: '/admin/*',
    methods: ["GET", "POST"],
    middleware: [
      (req, res, next) => console.log("Hi from route middleware")
    ],
  }
])

The order of registering route middleware is as follows:

  1. Route middleware from the core
  2. Route middleware from plugins (in the order plugins are registered)
  3. Route middleware from the app

API Routes

An API Route is always attached to a method, decided by the exported const from the route file. API Routes are registered by scanning the filesystem.

Two types of API Routes are supported:

  1. Static segments: /admin/products/route.ts
  2. Dynamic segments: /admin/products/[id]/route.ts

Sorting routes and middleware

Before registering routes + middleware with Express, they are sorted as described below:

  1. Global middleware
  2. Route middleware
  3. API Routes

On top of this, each segment from the API Route matcher is sorted by its specificity:

  1. Wildcard segments, e.g. /admin/products/*
  2. Regex segments, e.g. /admin/(product-types|product-collections)
  3. Static segments, e.g. /admin/products
  4. Dynamic segments, e.g. /admin/products/:id

Important note about middleware
Middleware never overrides any existing middleware; they are added to the stack of existing middleware. For example, if you define a custom validation middleware on an existing route, then both the original and the custom validation middleware will run.

Performance improvements

This release contains general and, in some areas, significant performance improvements. Over the past weeks, we have focused on removing performance bottlenecks in Medusa. While many of the improvements impacted the entire application, the most significant ones are for cart operations.

Here's a summary of the changes and improvements:

Application-level changes

  • Eliminated unnecessary module calls in cart workflows to ensure we don't create unused transactions and have redundant database communication
  • Minimized redundant data fetching in cart workflows, ensuring that only the data that is needed for the operation is fetched
  • Changed workflows to accept prefetched data instead of always fetching the data independently
  • Introduced finer control over cart refresh logic to ensure we don't perform unnecessary cart refreshes
  • Replaced the Redis del operation with the non-blocking delete unlink to reduce the load on Redis
  • Replaced inefficient soft and hard deletes by performing the operation in a single query instead of multiple
  • (Dashboard) Minimized redundant data fetching for dashboard pages

Database-level changes
These changes are a result of thorough testing of our own infrastructure. Therefore, they should be deemed a recommendation rather than an absolute configuration. Additionally, it is important to highlight that these tests were run against a Neon Database with a PgBouncer setup. Without PgBouncer and Neon, the configuration would likely look different.

// medusa-config.ts
export default defineConfig({
  projectConfig: {
    // ...other options
    databaseDriverOptions: {
      pool: {
        max: 8, // Fixed pool size of 8
        idleTimeoutMillis: 30000, // Close idle connections after 30 seconds
        connectionTimeoutMillis: 10000, // Fail on connection attempt after 10 seconds
        createRetryIntervalMillis: 100 // Time between connection retries
      },
    },
    ...
  },
}

Results

We have run two tests:

Test 1:

  • Operation: Add to cart
  • Database spec:
    • 0.25 CPU
    • 1 GB RAM
  • Server spec:
    • 1 CPU
    • 2 GB RAM
  • Load: 5 carts, operations for each cart run in parallel; for each cart, we add 100 items sequentially

p95: 1180ms -> 866ms
p50: 833ms -> 593ms

Test 2:

  • Operation: Add to cart
  • Database spec:
    • 0.5 CPU
    • 2 GB RAM
  • Server spec:
    • 1 CPU
    • 2 GB RAM
  • Load: 5 carts, operations for each cart run in parallel; for each cart, we add 100 items sequentially

p95: 1180ms -> 773ms
p50: 849 -> 537ms

Both tests show an approximate performance improvement of 30-35%.

Features

Bugs

  • fix(types, medusa): remove fulfillment and payment status filters from validator + http types by @shahednasser in #11604
  • fix(types): Allow providing either vite@5 or vite@6 for the peer dependency by @kasperkristensen in #11581
  • fix(promotion): scope uniqueness index to non deleted promotions by @riqwan in #11624
  • fix(core-flows): support 0 as a valid unit price for custom line items by @riqwan in #11631
  • fix(core-flows): unsafe access to a variant property by @fPolic in #11588
  • fix(medusa): deleting location level by @fPolic in #11630
  • fix: Exclude the health endpoint from being logged by @sradevski in #11650
  • fix(dashboard): display CG in PL configuration details by @fPolic in #11653
  • fix(dashboard): few admin bug fixes by @fPolic in #11655
  • fix(core-flows): return created order return from workflow by @fPolic in #11642
  • fix(dashboard): filter outbound shipping options in RMA by @fPolic in #11629
  • fix(dashboard): variant inventory section by @fPolic in #11669
  • fix(dashboard): bust variant inventory cache on inventory update by @riqwan in #11690
  • fix(types,medusa): calculate taxes for original price by @riqwan in #11692
  • fix(dashboard): Use derived state in DataTable by @kasperkristensen in #11487
  • fix(dashboard, core-flows, medusa): prevent creation of empty fulfillments by @fPolic in #11664
  • fix(order): summary raw totals by @carlos-r-l-rodrigues in #11712

Documentation

Chores

Other Changes

New Contributors

Full Changelog: v2.5.1...v2.6.0