I run Tinloof, a design and development studio that recently launched just weeks apart:
The releases made us reflect quite a bit on major ecommerce pain points and how Medusa addresses them, so here’s what I’ve learned.
Ecommerce is not one-size-fit-all
Imagine running an ecommerce business that supplies custom packaging, like printed cups or pizza boxes, for restaurants and cafes.
Your packaging is ordered in bulk and is an important part of your clients’ businesses because it serves as an extension of their branding.
Each order item requires collaboration with the customer at every step, from design approval to production and delivery.
You need a system where:
- Orders are split into tickets, each representing the journey of an order item.
- Clients can interact with these tickets and receive notifications about their progress.
- Customer support team manage these tickets until they’re fulfilled.
This is a nightmare on a traditional ecommerce platform where you’ll inevitably face one of two sorts:
- Either rely on another SaaS subscription if you’re lucky to find one that integrates with the platform.
- Or invest tremendous effort bending every single extensibility point the platform exposes just to end up with a solution that is clearly not built right and feels hastily patched together.
I can say that because a few months ago, we found ourselves in the exact same position when we discovered mid-project we had to build a ticket-based order management system for BrandYour that integrates with Shopify.
We made it work, but had we been familiar with Medusa, we would have been able to avoid most of the complexity we faced during implementation.
More broadly, the problem is that ecommerce platforms can’t keep up with the never-ending plethora of ecommerce use-cases.
Selling clothing differs vastly from selling custom packaging or furniture: ecommerce is not one-size-fits-all.
About Medusa
Medusa is an ecommerce platform that provides out of the box common ecommerce features such as product management, cart, orders, payment integrations, and many more.
We recently built a production-ready cookies store by relying solely on those features.
Medusa is different in the fact that it’s more than an ecommerce platform: it’s a first-generation ecommerce framework that provides business and technical building blocks to shape every aspect of your ecommerce based on your own unique business needs.
Let’s see what it looks like in practice to build a solid solution to handle our packaging orders.
1. Data modelling
With Medusa, you literally own the database.
You start by adding a data model for tickets that lives as first-class citizen on the same database as products, orders, and customers.
Tickets have:
- A status: Brief received, Designing artwork, Fulfilling, Delivered.
- An artwork brief filled up by the customer.
- Notes to hold communication between the customer and customer support team.
- A connection to an order item.
We also expose a service to interact with the data model:
Note that this is a simplified version, you can make even more robust by making sure for example that tickets have a history of changes both sides can access.
2. Admin UI
We have the option to add UI widgets to the Medusa admin pages like orders but we can do better than that: we’ll add an entire new page for managing tickets.
A Trello-like board is perfect to enable the customer support team to move tickets around until they’re fulfilled.
Since Medusa admin pages are just React components, I generated one with v0.
3. Expose endpoints for the admin and storefront
To make the Admin functional and make sure we’re all set for the storefront functionality, we expose API endpoints on Medusa to list tickets and manage them.
The best part is that most of the logic is already in place in the service we created in the first place.
Below an example of an API to list tickets. Since it’s created under Copy to clipboard/admin
it’s automatically protected and only accessible by admins or logged-in users:
1234567// api/admin/tickets.tsexport const GET = async (req: MedusaRequest, res: MedusaResponse) => {const ticketsService = req.scope.resolve("tickets_service");const tickets = await ticketsService.listAndCount();res.json({ tickets });};
4. Create tickets from new orders
To create tickets out of new orders, we’ll use a few Medusa primitives:
First, we create a subscriber that listens to Copy to clipboardorder_placed
events, emitted by built-in Order module when new orders are created and calls a workflow to create tickets.
1234567891011// modules/tickets/subscribers/order-created.tsexport default async function orderCreatedHandler({event,}: SubscriberArgs<{ id: string }>) {await createTicketsWorkflow(event.data.id);}export const config: SubscriberConfig = {event: ["order.placed"],};
Why not just call the Copy to clipboardTicketsService
directly?
Because workflows are much more powerful:
- They have clearly defined steps.
- Steps can be configured to automatically retry on failures: e.g. the Copy to clipboard
createTickets
step automatically retries up to 3 times with a 1 sec delay in case of failures. This makes it quite robust against random issues like connection interruptions. - Steps can have compensation logic that runs when the step fails: imagine if ticket creation partially failed? It’s quite handy to make the step automatically clear any tickets created before retrying to avoid duplicates.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758// modules/tickets/workflows/create-tickets.tsconst resolveOrderItemsStep = createStep("resolve-order-items",async (input: string, { container }) => {const orderService = container.resolve(Modules.ORDER);const items = await orderService.listOrderLineItems({order_id: input,});if (items.length === 0) {throw new Error("No items found");}return new StepResponse({ items });});const createTicketsStep = createStep("create-tickets",async (input: CreateTicketsInput, { container }) => {const ticketService: TicketsService = container.resolve("tickets_service");const tickets = input.items.map((item) => ({order_id: input.order_id,line_item: item,}));const ids = await ticketService.bulkCreateTickets(tickets);emitEventStep({eventName: "tickets_created",data: {ids,},});return new StepResponse({ ticket_ids: ids }, input.order_id);},async (input: string, { container }) => {const ticketService: TicketsService = container.resolve("tickets_service");await ticketService.deleteTicketsByOrderId(input);});const createTicketsWorkflow = createWorkflow("create-tickets",(input: string) => {const { items } = resolveOrderItemsStep(input);const createTicketsResponse = createTicketsStep({ order_id: input, items });return new WorkflowResponse(createTicketsResponse);});export default createTicketsWorkflow;
Note how when tickets are created, we emit a Copy to clipboardtickets_created
event that will come in handy in the next step.
5. Notify customers on ticket updates
When new tickets are created or updated, we want to notify the customer about them.
Similarly to the previous step, you can reliably implement that by:
- Adding subscribers for Copy to clipboard
tickets_created
and Copy to clipboardtickets_updated
events. - Make the subscribers call a workflow to notify the customer via email.
6. Add ticket information on customer accounts
Finally, we want to show tickets information on the customer account area.
We can then create the UI below and expose APIs to it from the tickets module to provide information such as:
- Tickets and their status.
- Any actions required by the user.
Exciting times for ecommerce
The future of ecommerce is businesses that can innovate by quickly iterating over solid solutions tailor-made for their use-case.
Medusa is the ecommerce framework we’ve been waiting for to make it happen.