Fresh Kitchen follows a three-tier architecture: a Next.js frontend, a Go/Goa backend API, and a MongoDB database. All three run as Docker Compose services and communicate over an internal Docker network.
Browser
│
│ HTTP/HTTPS
▼
Next.js (port 3000)
│ Auth: NextAuth.js + Google OAuth
│ API rewrites: /api/backend/* → http://backend:8080/api/*
▼
Go/Goa Server (port 8080)
│ Auth middleware validates Google ID token
│ Routes to 6 service implementations
│ Business logic (services/)
▼
MongoDB (port 27017)
└── freshkitchen database
├── users
├── menus
├── orders
├── schedule
├── announcements
└── settings
The backend uses Goa v3, a design-first Go framework. You write API contracts in a DSL, run goa gen, and get type-safe HTTP servers generated for you.
backend/
├── design/ API contracts (Goa DSL) — source of truth
├── gen/ Generated HTTP servers & types — do NOT edit manually
├── [service].go Goa service implementations (thin bridge layer)
├── services/ Business logic — validation, calculations, transformations
├── store/ Data access — all MongoDB queries live here
├── middleware/ Auth (Google OAuth token validation) and admin role check
└── cmd/ Entry point — wires everything together
Data flow for a request:
HTTP Request
→ CORS handler
→ Auth middleware (verifies Google ID token, injects user into context)
→ [Admin middleware if protected route]
→ Generated Goa HTTP handler (decodes request, validates types)
→ Service implementation (calls services/ layer)
→ services/ layer (business logic, calls store/)
→ store/ layer (MongoDB query)
→ Response encoded and returned
- Define the endpoint in
backend/design/<service>.gousing Goa DSL - Run
cd backend && goa gen freshkitchento regenerategen/ - Implement the method in
backend/<service>.go(bridge to services) - Add business logic in
backend/services/<service>.go - Add persistence in
backend/store/<service>.go
The frontend is a Next.js 14 app using the App Router. Pages are in src/app/, and all API calls go through a centralized client in src/lib/api.ts.
src/app/
├── page.tsx Customer home — weekly menu + cart
├── order/
│ ├── page.tsx Checkout — order review + payment
│ └── success/page.tsx Post-payment confirmation
├── admin/
│ ├── layout.tsx Admin shell (nav, auth guard)
│ ├── page.tsx Dashboard — analytics overview
│ ├── menu/ Menu editor
│ ├── schedule/ Day/week blocking
│ ├── announcements/ Announcement management
│ ├── bookings/ All orders view
│ └── admins/ Admin user management
└── api/auth/[...nextauth]/ NextAuth.js handler
- User clicks "Sign in with Google" — NextAuth.js initiates OAuth flow
- Google redirects back with an authorization code
- NextAuth.js exchanges it for tokens, stores the Google ID token in the session JWT
- All API calls include
Authorization: Bearer {idToken}header - Backend middleware validates the token against Google's OAuth endpoint and loads or creates the user record
All backend calls go through this single module. It automatically attaches the auth header from the active NextAuth session. Components import named functions (e.g., fetchWeekMenu, createOrder) rather than calling fetch directly.
NextAuth.js manages the OAuth session. The id_token from Google is stored in the session and exposed to pages via useSession().
backend/middleware/auth.go intercepts every protected request:
- Reads the
Authorization: Bearer <token>header - Validates the token with Google's token info endpoint
- Looks up or creates the user in MongoDB
- Injects the user struct into the request context
backend/middleware/admin.go is applied on top of auth for admin routes:
- Reads the user from context
- Returns
403 Forbiddenifuser.role != "admin"
The first user to authenticate is automatically granted role: admin by the user store (store/user.go). Subsequent users get role: customer. Admins can promote others via the Admin Users UI.
Stripe integration is optional and controlled by STRIPE_ENABLED=true.
When enabled:
- Customer clicks "Pay with Stripe" on the order page
- Frontend calls
POST /api/orders/checkout— backend creates a Stripe Checkout Session - Customer is redirected to Stripe-hosted checkout
- On success, Stripe sends a webhook to
POST /api/webhooks/stripe - Backend marks the order
status: paid
When disabled, orders are created with status: pending and no payment is collected.
services:
frontend: Next.js 14, port 3000
backend: Go/Goa server, port 8080
mongodb: MongoDB 7, port 27017Volumes:
mongo_data— persistent MongoDB storageuploads— menu images uploaded by admins
Network:
freshkitchenbridge network — services reach each other by service name (e.g.,mongodb:27017)
MongoDB initialization (mongo-init/init.js) runs on first startup and creates all collections, indexes, and default settings.