
E-commerce · Backend Migration
Reniom.com — Live Backend Migration
Replaced a live e-commerce site's Next.js/Prisma backend with a separate Django REST Framework service over live traffic — storefront, checkout, auth, and the entire admin panel now run on the new API, with zero visual change for shoppers.
Tech stack
The migration
Reniom originally ran on a Next.js/Prisma backend. It was fully replaced with a separate Django + Django REST Framework service on its own Railway Postgres database — storefront browsing, guest checkout, OTP login, the account page, and the entire admin panel now all go through the new API. Prisma was removed from the frontend entirely (no dependency, no schema, no client); the Next.js app on Vercel became a pure API client with no visual change for shoppers. It happened in phases over live traffic: scaffold and model recreation, storefront reads, cart and checkout, real OTP auth (replacing a temporary token stub), staff-only image upload to a Railway Volume (replacing Vercel Blob), a one-shot idempotent data migration from the old Prisma database, cutting the storefront over, staff-only CRUD, and finally guest checkout/contact/profile — at which point Prisma was dropped completely.
Why UUIDs
Every Django model uses a UUID primary key instead of an auto-incrementing integer, specifically so a future public API never exposes sequential, guessable IDs — matching Prisma's own opaque cuid() strings, so the ID shape a client sees never changed across the migration.
The slug problem
Guest checkout was deliberately built to take product slugs instead of database IDs, because carts saved in a visitor's browser before the cutover held old Prisma IDs that resolve to nothing under the new UUID scheme — slug is the one identifier stable across both systems.
Redefining PENDING
The PENDING order status was deliberately repurposed to mean "this is the customer's live cart" rather than "a placed order" (the old meaning under Prisma), with a one-pending-cart-per-customer database constraint; the one-shot migration script remaps old-meaning PENDING rows to CONFIRMED so order history stays correct.
Admin auth, deliberately separate
Admin auth was rebuilt on Django's own built-in user system instead of recreating the old custom AdminUser model, specifically because the OTP login flow creates real (non-staff) user accounts, and the admin login now explicitly requires is_staff — a deliberate safeguard so a customer's OTP session can never authenticate into an admin session.