Skip to content

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.
  1. 01 · Edge

    • Browser
    • Nginx reverse proxyrate limits: auth 10 req/min, API 200 req/min
  2. 02 · Frontend

    • Next.js 14 App Routerserver components for the home page; Zustand and Axios for cart, checkout, profile
  3. 03 · API

    • Go + Gin, three layershandlers, services, repositories; JWT middleware and RBAC
  4. 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
Runtime topology from the repository README: Nginx in front, the Next.js frontend and Go API behind it, PostgreSQL and Redis as stores, Stripe and the external catalog API as outside services.

Data flow

  1. 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.
  2. 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.
  3. 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
HELPkeybindings~/prtf/help.md

Keyboard

g h
home
g a
ai workflow
g w
work
g l
home lab
g s
skills
g e
experience
g c
contact
g r
resume
:
focus the shell
?
toggle this help
Esc
close

Shortcuts are ignored while typing in a field.