GoShop
A production-ready e-commerce application built with Go (REST + gRPC backend) and React (web frontend).
Architecture
The application runs two servers concurrently (backend) plus a React web frontend:
- HTTP (REST) — Gin framework, port
8888 - gRPC — port
8889, with JWT auth interceptor
Each domain (user, product, order, payment, notification) follows a ports-and-adapters layout:
internal/{domain}/
├── model/ # GORM models
├── dto/ # Request/response structs with validation tags
├── repository/ # Database access (depends on dbs.Database interface)
├── service/ # Business logic (depends on repository interfaces)
└── port/
├── http/ # Gin handlers and route registration
└── grpc/ # gRPC handlers and server registration
| Domain | HTTP | gRPC |
|---|---|---|
| user | ✓ | ✓ |
| product | ✓ | ✓ |
| order | ✓ | ✓ |
| payment | ✓ | — |
| notification | ✓ | — |
Tech Stack
Backend
| Concern | Library |
|---|---|
| HTTP framework | Gin v1.12 |
| gRPC | grpc-go v1.79 |
| ORM | GORM v1.31 + PostgreSQL |
| Cache | go-redis v9 |
| Auth | JWT (golang-jwt v5) |
| Validation | gocommon/validation |
| API Docs | Swagger |
| Testing | testify v1.11 + mockery |
| Proto codegen | buf + protobuf v1.36 |
Frontend
| Concern | Library |
|---|---|
| Framework | React 18 + TypeScript |
| Build tool | Vite |
| Styling | Tailwind CSS |
| Routing | React Router v6 |
| Data fetching | TanStack Query |
| Forms | React Hook Form + Zod |
| HTTP client | Axios |
Prerequisites
- Go 1.26+
- Node.js 18+
- PostgreSQL
- Redis
Docker Compose for local dependencies: docker-compose-template
Getting Started
1. Clone and configure
git clone https://github.com/quangdangfit/goshop.git
cd goshop
cp config.sample.yaml config.yaml
Edit config.yaml (lives at the repo root and is loaded from the working directory; override with CONFIG_FILE=/path/to/config.yaml):
environment: production
http_port: 8888
grpc_port: 8889
auth_secret: your-secret-key
database_uri: postgres://username:password@localhost:5432/goshop
redis_uri: localhost:6379
redis_password:
redis_db: 0
# Stripe (payments)
stripe_secret_key: sk_test_xxx
stripe_webhook_secret: whsec_xxx
stripe_publishable_key: pk_test_xxx
# SMTP (notifications — point at MailHog locally: host=localhost, port=1025)
smtp_host:
smtp_port: 25
email_from: [email protected]
2. Apply database migrations
The app no longer runs AutoMigrate — schema lives in versioned SQL files under
migrations/ and is applied with golang-migrate.
brew install golang-migrate # or: go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
DATABASE_URI="postgres://username:password@localhost:5432/goshop?sslmode=disable" make migrate-up
See migrations/README.md for conventions and the
production / Kubernetes init-container pattern.
3. Run the backend
go run cmd/api/main.go
INFO HTTP server is listening on PORT: 8888
INFO GRPC server is listening on PORT: 8889
4. Run the web frontend
cd web
npm install
npm run dev
Web UI: http://localhost:3000
The frontend proxies all
/apirequests to the backend athttp://localhost:8888, so both servers must be running.
5. Browse the API
Swagger UI: http://localhost:8888/swagger/index.html
API Reference
Auth
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Register |
| POST | /api/v1/auth/login |
Login |
| POST | /api/v1/auth/refresh |
Refresh access token |
| GET | /api/v1/auth/me |
Get current user |
| PUT | /api/v1/auth/change-password |
Change password |
Addresses
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/addresses |
List my addresses |
| POST | /api/v1/addresses |
Create address |
| GET | /api/v1/addresses/:id |
Get address |
| PUT | /api/v1/addresses/:id |
Update address |
| DELETE | /api/v1/addresses/:id |
Delete address |
| PUT | /api/v1/addresses/:id/default |
Set default address |
Wishlist
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/wishlist |
Get my wishlist |
| POST | /api/v1/wishlist |
Add product to wishlist |
| DELETE | /api/v1/wishlist/:productId |
Remove from wishlist |
Categories
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/categories |
List categories |
| GET | /api/v1/categories/:id |
Get category |
| POST | /api/v1/categories |
Create category (auth) |
| PUT | /api/v1/categories/:id |
Update category (auth) |
| DELETE | /api/v1/categories/:id |
Delete category (auth) |
Products
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/products |
List products (cached) |
| GET | /api/v1/products/:id |
Get product (cached) |
| POST | /api/v1/products |
Create product (auth) |
| PUT | /api/v1/products/:id |
Update product (auth) |
| GET | /api/v1/products/:id/reviews |
List product reviews |
| POST | /api/v1/products/:id/reviews |
Create review (auth) |
| PUT | /api/v1/products/:id/reviews/:reviewId |
Update review (auth) |
| DELETE | /api/v1/products/:id/reviews/:reviewId |
Delete review (auth) |
Orders
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/orders |
Place order |
| GET | /api/v1/orders |
List my orders |
| GET | /api/v1/orders/:id |
Get order details |
| PUT | /api/v1/orders/:id/cancel |
Cancel order |
| PUT | /api/v1/orders/:id/status |
Update order status (admin) |
Coupons
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/coupons |
Create coupon (auth) |
| GET | /api/v1/coupons/:code |
Get coupon by code |
Payments
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/orders/:id/payment-intent |
Create Stripe PaymentIntent for order |
| POST | /api/v1/webhooks/stripe |
Stripe webhook (signature-verified, no JWT) |
| GET | /api/v1/config/public |
Public client config (Stripe publishable key) |
Notifications
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/me/notification-preferences |
Get my notification preferences |
| PUT | /api/v1/me/notification-preferences |
Update notification preferences |
Cart is client-side only (persisted in the browser). Order creation accepts the full line items and the server re-validates products, prices, and stock.
Development
Run all unit tests with coverage
make unittest
Run a single test suite
go test ./internal/product/service/... -v -run TestProductServiceTestSuite
Run a single test case
go test ./internal/product/service/... -v -run TestProductServiceTestSuite/TestCreateSuccess
Run integration tests
Integration suites live under tests/integration/ (per-domain: order,
payment, user, product, notification), are gated by the
//go:build integration tag, and use testcontainers
to spin up real Postgres / Redis / MailHog. They are invisible to make unittest / go test ./....
make integration
# = go test -tags=integration -timeout 9000s -v -coverprofile=coverage.integration.out ./tests/integration/...
Requirements: a running Docker daemon. The shared helpers (StartPostgres,
StartRedis, NewHTTPEnv) live in tests/testutil/. CI runs the
integration job in parallel with the unit job; both upload coverage to
Codecov under the unittest / integration flags.
Database migrations
make migrate-up # apply all pending migrations
make migrate-down # roll back the latest migration
make migrate-status # print current schema version
make migrate-new name=add_index # scaffold the next NNNN_*.{up,down}.sql pair
Set DATABASE_URI in your shell to override the default
(postgres://postgres:test@localhost:5432/goshop?sslmode=disable).
Regenerate mocks
make mock
Regenerate Swagger docs
make doc
Regenerate proto (Uses https://buf.build)
cd proto && make build