Elantika
The online store for a handcrafted bag label: guest checkout, Razorpay payments, variant-level stock and Shiprocket shipping, with an admin panel the brand runs day to day.

Overview
Élantika is the online store for a handcrafted bag label, live at elantika.com. Shoppers browse the collection, check out as a guest or with an account, pay through Razorpay, and track their parcel. Behind the storefront there's an admin panel the brand runs day to day: products and variants, stock, coupons, banners, customers, orders, and Shiprocket shipping. The storefront is Next.js on Vercel, and the API is a separate Express and TypeScript service on Render backed by MongoDB.
The problem
A small label selling direct needs more than a product grid. Payments have to be verified on the server, stock has to stay correct across sizes and colours, and every paid order has to reach a courier without someone retyping addresses into a shipping dashboard. Off-the-shelf store builders could do that, but with monthly fees and little control over the look, and the brand wanted an editorial storefront it owns.
What I built
- A storefront with a full-bleed editorial hero, a shop page with category and price filters, product pages with image galleries, a slide-out cart drawer, and smooth scrolling (Lenis, GSAP, Framer Motion).
- Guest checkout: carts are keyed by an
x-session-idheader for guests and by customer id once signed in, and they store price, title and image snapshots so a later price edit can't change what's already in someone's cart. - Products with variants: each variant has its own SKU, price, tax rate and tracked stock. Every stock change is written to an
InventoryMovementledger linked to the order that caused it. - Server-side order totals: subtotal, a site-wide tax rate, percentage or flat coupons with minimum-order and maximum-discount rules, and free shipping above a threshold set in the admin.
- Razorpay payments: the API creates the Razorpay order with the amount it calculated, then verifies the payment signature on the server before marking the order paid.
- Shiprocket fulfilment: a paid order is pushed to Shiprocket automatically. From the admin, staff assign a courier and generate the airway bill (AWB) and label, then schedule pickups, print manifests and invoices, and sync statuses back. Customers see their AWB on the order page.
- An admin panel with a revenue and top-products dashboard, product and category editors with Cloudinary image uploads, banners, coupons, customers and site settings, protected by separate admin JWTs and roles.
- SEO: per-product Open Graph images, a sitemap, robots rules and an
llms.txt.
Screens

Architecture
The Next.js storefront and admin panel both talk to one Express API, which owns pricing, stock, payments and shipping.
flowchart LR
subgraph client["Next.js on Vercel"]
store["Storefront and cart"]
admin["Admin panel"]
end
subgraph server["Express API on Render"]
auth["JWT auth and guest sessions"]
catalog["Products, variants, categories"]
orders["Cart, orders and coupons"]
ops["Dashboard and settings"]
end
subgraph data["Data"]
db[("MongoDB")]
media["Cloudinary"]
end
subgraph ext["External"]
pay["Razorpay"]
ship["Shiprocket"]
end
store -->|"x-session-id or customer JWT"| auth
admin -->|"admin JWT"| auth
auth --> catalog
auth --> orders
auth --> ops
catalog --> db
catalog --> media
orders --> db
orders --> pay
orders --> ship
ops --> dbSystem design
What happens from "Place order" to a parcel being booked.
sequenceDiagram
participant S as Shopper
participant F as Storefront
participant A as Express API
participant D as MongoDB
participant R as Razorpay
participant SR as Shiprocket
S->>F: Place order
F->>A: POST orders with address
A->>D: Load cart, check variant stock
A->>A: Subtotal, tax, coupon, shipping
A->>D: Create order as pending
A->>R: Create Razorpay order for the total
R-->>A: Razorpay order id
A-->>F: Order and Razorpay details
F->>R: Razorpay checkout
R-->>F: Payment id and signature
F->>A: POST verify-payment
A->>A: Verify signature
A->>D: Mark paid
A->>SR: Create shipment
A->>D: Decrement stock, log movements, clear cart
A-->>F: Order confirmed
Note over A,SR: If Shiprocket fails the order stays paid and staff retry from the adminDecisions & trade-offs
- A separate Express API instead of Next.js route handlers. Payments, stock and shipping logic live in one service that's easy to test with scripts, and the admin and storefront share it. The cost is two deployments, and Render's free tier sleeps when idle, so the first request after a quiet spell is slow.
- Saving the paid status before calling Shiprocket. A courier outage should never make a paid order look unpaid, so the shipment push happens after the save, and failures are retried from the admin.
- Price snapshots in the cart. What the shopper saw is what they pay, even if a price changes mid-session. The order is still totalled on the server, so the browser never decides the amount.
- Payment confirmation depends on the browser calling
verify-payment. It's simple, but a shopper who closes the tab right after paying leaves a pending order. A Razorpay webhook would close that gap. - Stock is checked when the order is created but only decremented after payment. Two shoppers buying the last unit at the same moment could both pay. A short reservation, or a conditional decrement, would prevent that.
What I'd do next
- Add Razorpay webhooks so payment status doesn't depend on the browser.
- Reserve stock at checkout with an expiry, and decrement it with a conditional update.
- Show live courier rates at checkout. The Shiprocket service already has a rates call; it isn't wired into the checkout yet.