Building a Full-Stack E-commerce Platform with Next.js, Express, PostgreSQL, and Redis
A full-stack e-commerce application is more than a product listing page and a shopping cart. It needs to manage users, protect private operations, process orders, handle payments, and keep inventory consistent.
While building ShopSphere, I worked on bringing these pieces together in a single application. The project uses Next.js and TypeScript for the frontend, Node.js and Express for backend APIs, PostgreSQL with Prisma for data persistence, Redis for caching, and Razorpay for payment processing.
In this article, I'll explain the architecture, important engineering decisions, and lessons from building the application.
1. Understanding the architecture
ShopSphere separates the user interface from the backend services.
- Next.js and TypeScript: Provide the application interface, product pages, cart, checkout, order history, and admin dashboard.
- Node.js and Express: Handle API requests, authentication, validation, and business logic.
- PostgreSQL and Prisma: Store structured application data and provide an ORM for database operations.
- Redis: Cache frequently requested product information and selected dashboard results.
- Razorpay: Handles the payment checkout flow.
Separating these responsibilities makes the application easier to maintain. For example, the frontend can display products without needing to know how the backend queries PostgreSQL.
2. Authentication and authorization are different
One important lesson from ShopSphere was that authentication and authorization solve different problems.
Authentication answers: Who is the user?
Authorization answers: What is this user allowed to do?
The application uses JWT-based authentication with short-lived access tokens and refresh tokens stored in HTTP-only cookies. The access token is sent through the Authorization header when calling protected APIs.
Role-based authorization adds another layer. A customer should be able to manage their cart and view their orders, while administrative operations—such as managing the product catalog—must be restricted to authorized administrators.
The backend must enforce these permissions. Hiding an admin button in the frontend is not sufficient because a user can still attempt to call the API directly.
3. Improving response times with Redis
Product listings and product details are requested frequently, so repeatedly querying the database for unchanged information can introduce unnecessary work.
ShopSphere uses Redis to cache selected product data.
A simplified cache-aside flow looks like this:
- Check whether the requested data exists in Redis.
- If it exists, return the cached result.
- If it does not, query PostgreSQL.
- Store the result in Redis and return it.
- When the underlying product data changes, invalidate the affected cache entries.
The last step is important. Caching without invalidation can cause users to see outdated prices, product information, or stock details.
Caching is useful for frequently read data, but it should not replace database validation for operations such as checkout.
4. Handling payments safely
Payment processing requires particular care because an incorrect implementation can leave an order and its payment status inconsistent.
The ShopSphere checkout flow creates an order in a pending state and initiates a Razorpay order. After the checkout process, the backend verifies the payment signature before marking the order as paid.
The backend also uses a Prisma database transaction to update the payment-related order state and decrement inventory atomically.
Why does this matter?
Imagine an order contains the last available unit of a product. Payment verification and inventory updates must be handled carefully so that concurrent checkouts do not accidentally oversell stock.
A database transaction helps ensure that related database changes succeed together or roll back together. It does not, by itself, make an external payment operation atomic with the database; payment verification and failure handling still need to be designed correctly.
For a production system, I would also pay close attention to duplicate callbacks, idempotency, transaction failures, and reconciliation between the payment provider and the application.
5. Making the application useful for administrators
ShopSphere also includes an administrative dashboard that displays information such as revenue, order status, top products, low-stock alerts, and customer counts.
This transforms the application from a basic shopping interface into a tool for managing the business.
The dashboard combines backend aggregation with frontend visualizations. Frequently requested summary data can be cached briefly, provided that the application handles invalidation and freshness appropriately.
6. What I learned
Building ShopSphere reinforced several practical lessons:
- Authentication must be complemented by server-side authorization.
- Cache invalidation is part of the feature, not an afterthought.
- Payment status should be verified by the backend rather than trusted from the browser.
- Related inventory and order updates require careful transaction handling.
- A clear separation between frontend, API, and persistence responsibilities makes debugging easier.
Conclusion
ShopSphere helped me understand how frontend development, backend APIs, databases, caching, authentication, and payment integrations work together in a full-stack product.
The biggest takeaway is that a working interface is only one part of a reliable application. Correct data handling, security, and failure management are equally important.
Project: ShopShpere
Live Demo:Github
Technologies: Next.js, TypeScript, Express, PostgreSQL, Prisma, Redis, and Razorpay.
Top comments (0)