How Do You Handle Product Stock?

I’m just curious, how do you manage your product stock/quantities in Directus? I’ve had this question on my mind for a while when I thought about making inventory system, and would love to hear some opinions or best practices from the community.

A couple of the challenges I’ve bumped into:

  • Race conditions: Say multiple users are booking the same product at the same time, how do you keep the stock quantity totally accurate and make sure those bookings don’t overlap or mess up the numbers?

  • Order management: When updating quantities, deleting/adding products within an order, how do you make sure the stock itself always stays correct?

I’d love to hear what’s worked for you! Whether it’s a specific hook strategy, a custom endpoint, a cool trick with flows, or even an external tool you integrate with, I’m all ears!

Heyo :waving_hand: – some really good questions here.

Obviously, there’s nothing explicitly built into Directus for managing inventory, so you’d be building solutions for these yourself.

I would definitely be looking at custom event hooks through extensions. Here’s some pseudo code of what it might look like for something like that:

// You can leverage the database instance, which is just the 
// Knex instance that we use within our own services
// extensions/hooks/inventory-management/index.js
export default ({ filter, action }) => {
  // Use filter to intercept BEFORE the item is created
  filter('items.create', async (payload, meta, context) => {
    if (meta.collection === 'order_items') {
      const { database, schema, accountability } = context;
      
      await database.transaction(async (trx) => {
        // Lock the product row for update
        const product = await trx('products')
          .where('id', payload.product_id)
          .forUpdate()
          .first();
          
        if (!product) {
          throw new Error('Product not found');
        }
        
        if (product.stock_quantity < payload.quantity) {
          throw new Error(`Insufficient stock. Available: ${product.stock_quantity}, Requested: ${payload.quantity}`);
        }
        
        // Update stock atomically within the same transaction
        await trx('products')
          .where('id', payload.product_id)
          .decrement('stock_quantity', payload.quantity);
      });
    }
    
    return payload; // Important: return the payload for filter hooks
  });

The normal behavior is “whoever saves first wins” approach, but using database transactions should get you closer to what you’re after or throw an error if there’s insufficient inventory.

The other side of the coin is, of course, you could build just about anything with Directus, but that doesn’t mean that’s always the best course of action.

If you’re building a really complicated e-commerce system where you’re managing inventory of hundreds or thousands of products, it might be best to use an already existing dedicated e-commerce solution rather than having to rewrite (and maintain :sweat_smile:) all of that logic yourself.

Something like Commerce Layer or Medusa, for example. You can leverage Directus for product management and website content. And then use headless ecommerce systems to manage all the inventory and transactional lgoic.

Hopefully that’s helpful! :sign_of_the_horns:

Hey @Abdallah, I’m asking Bryant to take a look at your questions since he’s built a few of them out as examples!

I want to pop in and add there’s a few relevant 100 apps 100 hours episodes that folks who stumble onto this question might find helpful to watch: building an Equipment Booking Manager, a Product Information Management system and a Swag platform.