case study · personal project
Toy Store: Full-Stack E-commerce
A full-stack e-commerce application with a Go API, a Next.js 14 frontend, PostgreSQL, Redis-backed sessions and caching, Stripe checkout, and an admin dashboard, run as one Docker Compose stack behind Nginx.
- role:
- Developer
- context:
- Personal project
- period:
- University project
- stack:
- Go 1.23GinNext.js 14TypeScriptPostgreSQL 16Redis 7NginxDocker ComposeStripeJWTZustandZodTailwind CSS
Problem and context
Built as a university web-application project, but designed like a production system: separate layers, real authentication, a real payment flow (Stripe test mode), caching, rate limiting, and a single-command startup.
The catalog does not live in the app's own database. Toys come from an external API and are cached in Redis, which shaped several decisions: order snapshots, cache-aside reads, and graceful degradation when the cache is down.
Goals
- A complete purchase flow: browse, cart, wishlist, checkout, order history, cancellation requests.
- Authentication that can actually log users out, despite JWTs being stateless.
- Keep the external catalog fast, and keep the app usable when Redis is unavailable.
- Admin tooling for orders, users, and cancellation approvals.
My role
Built the Go API, the Next.js frontend, the database schema and migrations, and the Docker Compose deployment, and documented the architecture in the repository.
Users and stakeholders
- Shoppers browsing, buying, and tracking orders
- Admins managing orders, users, and cancellation requests
What was built
- Product catalog from an external API with Redis caching and filtering
- Cart and wishlist persisted per user
- Two-step Stripe PaymentIntent checkout; card data never touches the backend
- Dual-token JWT auth: 15-minute access token, 7-day refresh token stored in Redis with rotation on refresh
- Role-based access (user, admin) enforced in middleware
- Admin dashboard: orders, users, cancellation-request approval
- Nginx reverse proxy with rate limiting and a health-checked startup order in Docker Compose
Technical architecture
- Nginx is the single public entry point, with rate limits on auth endpoints and on the rest of the API.
- Go with Gin in three layers: HTTP handlers, services for business logic, repositories for SQL.
- Next.js 14 App Router: server components for the home page (SSR, server-side fetch inside the Docker network), client components with Zustand and Axios for cart, checkout, and profile.
- PostgreSQL 16 for users, orders, order items, cart items, and wishlist items, with golang-migrate migrations run at startup.
- Redis 7 for refresh tokens and the catalog cache.
01 · Edge
- Browser
- Nginx reverse proxyrate limits: auth 10 req/min, API 200 req/min
02 · Frontend
- Next.js 14 App Routerserver components for the home page; Zustand and Axios for cart, checkout, profile
03 · API
- Go + Gin, three layershandlers, services, repositories; JWT middleware and RBAC
04 · Stores and services
- PostgreSQL 16users, orders, order items, cart, wishlist
- Redis 7refresh tokens; catalog cache, 5-minute TTL
- StripePaymentIntent; card tokenized in the browser
- External toy catalog APIread through the Redis cache-aside layer
Data flow
- Catalog: request → Redis (5-minute TTL) → on a miss, the external toy API. If Redis is down, the request continues without caching and logs a warning.
- Checkout: the frontend asks the API for a PaymentIntent, Stripe.js tokenizes the card in the browser, and the API verifies the intent succeeded before creating the order with line-item snapshots.
- Auth: login issues an access token and a refresh token; the Axios interceptor refreshes transparently on 401; logout deletes the refresh token from Redis.
Key implementation details
- Line-item snapshots: orders store name, price, and image at purchase time, because the external catalog can change or remove toys.
- UUID primary keys to avoid ID enumeration.
- bcrypt with cost factor 12 for passwords.
- Zod validation on every form, with Serbian error messages; next-themes dark mode.
- Docker Compose startup order: PostgreSQL → Redis → API (runs migrations, exposes /health) → frontend → Nginx.
Engineering decisions
- Refresh tokens in Redis
- JWTs cannot be revoked on their own. Storing the refresh token server-side with a TTL makes logout real: no token in Redis, no session.
- Snapshot order lines
- The catalog is external and can change. Paid orders must show exactly what was bought at the price it was bought.
- Rate limiting in Nginx, not in Go
- Abusive traffic is dropped before it allocates anything in the application process.
- Three-layer backend
- Handlers, services, and repositories can be tested and changed independently.
Challenges and trade-offs
- Search is in-memory filtering and catalog pagination is client-side, which will not scale past a small catalog.
- Stripe webhooks need the Stripe CLI locally; the stack runs over HTTP with no TLS configured.
- No email notifications.
Results and impact
- A complete, documented e-commerce stack that starts with one command and demonstrates production patterns end to end: layered backend, revocable JWT sessions, cache-aside reads with graceful degradation, and a PCI-friendly payment flow.
Technologies
- Go 1.23
- Gin
- Next.js 14
- TypeScript
- PostgreSQL 16
- Redis 7
- Nginx
- Docker Compose
- Stripe
- JWT
- Zustand
- Zod
- Tailwind CSS