- Project Overview
- Features Implemented
- Quick Start
- Project Structure
- API Endpoints
- Database Schema
- Authentication & Security
- Testing with REST Client
- Understanding Go Patterns
- Configuration System
- Error Handling
- Development Workflow
A production-ready REST API e-commerce backend built with Go, featuring secure authentication, comprehensive product management, and robust error handling.
- Backend: Go with Gorilla Mux router
- Database: MySQL with golang-migrate for schema management
- Authentication: JWT tokens with bcrypt password hashing
- Testing: Built-in testing with REST Client integration
- Config: Environment-based configuration with .env support
- User Registration with validation and duplicate email checking
- User Login with JWT token generation
- Password Security using bcrypt hashing
- Input Validation with proper error messages
- Get All Products with proper price handling (DECIMAL support)
- Get Product by ID with error handling for invalid IDs
- Create Products with JSON validation
- Image URL support for product images
- Generic type conversion for database queries
- Cart Management with user-specific carts
- Order Processing with order items tracking
- Database Relationships with proper foreign keys
- JWT Authentication with configurable secrets
- Password Hashing with bcrypt (cost 10)
- Input Sanitization and validation
- Error Response Standardization
- Schema Management with golang-migrate
- Foreign Key Constraints for data integrity
- Proper Data Types (DECIMAL for prices, TIMESTAMP for dates)
- Rollback Support with down migrations
- REST Client Testing with comprehensive test scenarios
- Environment Configuration with .env file support
- Makefile Commands for easy development workflow
- Comprehensive Documentation with examples
- Go 1.19+ installed
- MySQL server running
- Git (for version control)
- Clone and Setup
git clone <repository-url>
cd Ecom
go mod tidy- Database Setup
# Create database
mysql -u root -p
CREATE DATABASE ecom;
exit
# Run migrations
make migrate-up- Environment Configuration
# Create .env file
cp .env.example .env
# Edit .env with your database credentials
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=ecom
DB_HOST=127.0.0.1
DB_PORT=3306
JWT_SECRET=your-secret-key
JWT_EXPIRATION=72h- Start Server
make run
# Server starts on http://localhost:8080- Test API
# Health check
curl http://localhost:8080/health
# Or use the REST Client extension with api-tests.httpEcom/
├── cmd/ # Application entry points
│ ├── main.go # 🚀 Application startup
│ ├── api/
│ │ └── api.go # HTTP server and route setup
│ ├── check/ # Database status checker
│ └── migrate/
│ ├── main.go # Migration runner
│ └── migrations/ # Database schema migrations
│ ├── 20251015194357_add-user-table.{up,down}.sql
│ ├── 20251016080720_add-products.{up,down}.sql
│ ├── 20251016080810_add-orders.{up,down}.sql
│ ├── 20251016080829_add-order-items.{up,down}.sql
│ └── 20251022000001_add_image_url_to_products.{up,down}.sql
├── config/ # Configuration management
│ └── env.go # Environment variables loader
├── db/ # Database connection
│ └── db.go # MySQL connection setup
├── types/ # Data models and interfaces
│ └── types.go # User, Product, Order, Cart structs & interfaces
├── services/ # Business logic services
│ ├── user/ # User authentication & management
│ │ ├── routes.go # Registration, login handlers
│ │ ├── routes_test.go # Unit tests for user handlers
│ │ └── store.go # User database operations
│ ├── product/ # Product management
│ │ ├── routes.go # CRUD product handlers
│ │ └── store.go # Product database operations
│ ├── cart/ # Shopping cart functionality
│ │ ├── routes.go # Cart checkout handler
│ │ └── service.go # Cart business logic
│ ├── order/ # Order management
│ │ └── store.go # Order database operations
│ └── auth/ # Authentication utilities
│ ├── jwt.go # JWT token management & middleware
│ └── password.go # Password hashing utilities
├── utils/ # Helper functions
│ └── utils.go # JSON parsing, validation, response helpers
├── api-tests.http # REST Client test scenarios
├── .env # Environment variables
├── Makefile # Development & migration commands
├── go.mod # Go module dependencies
├── go.sum # Dependency checksums
├── test-json.go # JSON testing utility
└── DOCUMENTATION.md # This file
Register a new user account.
Request:
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"password": "password123"
}Response (201 Created):
nullError Response (400 Bad Request):
{
"error": "invalid payload [FirstName: 'required' tag failed]"
}Error Response (409 Conflict):
{
"error": "user with email john@example.com already exists"
}Authenticate user and receive JWT token.
Request:
{
"email": "john@example.com",
"password": "password123"
}Response (200 OK):
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Error Response (401 Unauthorized):
{
"error": "invalid email or password"
}Get all products (public endpoint).
Response (200 OK):
[
{
"id": 1,
"name": "iPhone 15",
"description": "Latest Apple smartphone",
"image_url": "https://example.com/iphone15.jpg",
"price": 999.99,
"quantity": 50,
"created_at": "2025-10-15T08:00:00Z"
}
]Get specific product by ID (public endpoint).
Response (200 OK):
{
"id": 1,
"name": "iPhone 15",
"description": "Latest Apple smartphone",
"image_url": "https://example.com/iphone15.jpg",
"price": 999.99,
"quantity": 50,
"created_at": "2025-10-15T08:00:00Z"
}Error Response (400 Bad Request):
{
"error": "invalid product ID"
}Create a new product (admin only).
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Request:
{
"name": "iPhone 15",
"description": "Latest Apple smartphone",
"image_url": "https://example.com/iphone15.jpg",
"price": 999.99,
"quantity": 50
}Response (201 Created):
{
"message": "Product created successfully"
}Error Response (403 Forbidden):
{
"error": "permission denied"
}Process cart checkout and create order.
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Request:
{
"items": [
{
"product_id": 1,
"quantity": 2
},
{
"product_id": 2,
"quantity": 1
}
],
"address": "123 Main St, City, Country"
}Response (200 OK):
{
"order_id": 123,
"total_amount": 1999.98
}CREATE TABLE users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY (email)
);CREATE TABLE products (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
quantity INT UNSIGNED NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
image_url VARCHAR(255) DEFAULT NULL -- Added via migration
);CREATE TABLE orders (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
total DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'completed', 'cancelled') DEFAULT 'pending',
address TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);CREATE TABLE order_items (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
order_id INT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);- 20251015194357: Added users table with authentication fields
- 20251016080720: Added products table with DECIMAL pricing
- 20251016080810: Added orders table with user relationships
- 20251016080829: Added order_items table with foreign keys
- 20251022000001: Added image_url column to products table
- Algorithm: HMAC-SHA256 (HS256)
- Token Expiration: Configurable via
JWT_EXPIRATION_IN_SECONDS(default: 7 days) - Secret Key: Environment variable
JWT_SECRET(fallback: "not-so-secret-now-is-it?") - Claims: User ID and expiration time
- Middleware:
WithJWTAuthfunction for protecting routes
{
"user_id": "123",
"exp": 1698765432
}POST /api/v1/products- Create product (admin functionality)POST /api/v1/cart/checkout- Process checkout
- Hashing: bcrypt with default cost factor
- Validation: Minimum 6 characters, maximum 50 characters
- Storage: Only hashed passwords stored in
password_hashcolumn
- JSON Validation: Using
go-playground/validatorwith struct tags - Email Format: RFC-compliant email validation
- Required Fields: Server-side validation with detailed error messages
- SQL Injection Prevention: Parameterized queries only
func WithJWTAuth(handlerFunc http.HandlerFunc, store UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Extract token from Authorization header
tokenString := utils.GetTokenFromRequest(r)
// Validate JWT token
token, err := validateJWT(tokenString)
if err != nil || !token.Valid {
permissionDenied(w)
return
}
// Extract user ID from claims
claims := token.Claims.(jwt.MapClaims)
userID, _ := strconv.Atoi(claims["userID"].(string))
// Add user to request context
ctx := context.WithValue(r.Context(), UserKey, userID)
handlerFunc(w, r.WithContext(ctx))
}
}The project includes api-tests.http for comprehensive API testing:
### Register User
POST {{baseUrl}}/api/v1/register
Content-Type: application/json
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"password": "password123"
}### Login User
POST {{baseUrl}}/api/v1/login
Content-Type: application/json
{
"email": "john.doe@example.com",
"password": "password123"
}### Get All Products
GET {{baseUrl}}/api/v1/products
### Get Single Product
GET {{baseUrl}}/api/v1/products/1
### Create Product (Protected)
POST {{baseUrl}}/api/v1/products
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "iPhone 15",
"description": "Latest Apple smartphone",
"price": 999.99,
"quantity": 50,
"image_url": "https://example.com/iphone15.jpg"
}### Process Checkout (Protected)
POST {{baseUrl}}/api/v1/cart/checkout
Content-Type: application/json
Authorization: Bearer {{token}}
{
"items": [
{
"product_id": 1,
"quantity": 2
}
],
"address": "123 Main St, City, Country"
}- ✅ Invalid email format validation
- ✅ Missing required fields validation
- ✅ Duplicate email registration prevention
- ✅ Invalid login credentials handling
- ✅ Missing JWT token authorization
- ✅ Invalid product ID handling
- ✅ JSON parsing error handling
// Interface defines contract
type UserStore interface {
GetUserByEmail(email string) (*User, error)
CreateUser(user *User) error
}
// Implementation handles database
type Store struct {
db *sql.DB
}
func (s *Store) CreateUser(user *User) error {
_, err := s.db.Exec(
"INSERT INTO users(first_name, last_name, email, password_hash) VALUES(?, ?, ?, ?)",
user.FirstName, user.LastName, user.Email, user.Password,
)
return err
}type Handler struct {
store UserStore // Dependency injection
}
func (h *Handler) handleRegister(w http.ResponseWriter, r *http.Request) {
// 1. Parse request
var payload RegisterUserPayload
if err := utils.ParseJSON(r, &payload); err != nil {
utils.WriteError(w, http.StatusBadRequest, err)
return
}
// 2. Validate business rules
if payload.Email == "" {
utils.WriteError(w, http.StatusBadRequest, errors.New("email required"))
return
}
// 3. Process business logic
user := &User{
FirstName: payload.FirstName,
LastName: payload.LastName,
Email: payload.Email,
Password: hashedPassword,
}
// 4. Store data
if err := h.store.CreateUser(user); err != nil {
utils.WriteError(w, http.StatusInternalServerError, err)
return
}
// 5. Return response
utils.WriteJSON(w, http.StatusCreated, map[string]string{
"message": "User created successfully",
})
}func withJWTAuth(handlerFunc http.HandlerFunc, store UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Extract and validate JWT token
tokenString := getTokenFromRequest(r)
token, err := validateJWT(tokenString)
if err != nil {
permissionDenied(w)
return
}
// Add user to request context
ctx := context.WithValue(r.Context(), "userID", token.UserID)
handlerFunc(w, r.WithContext(ctx))
}
}# Database Configuration
DB_USER=root
DB_PASSWORD=your_mysql_password
DB_NAME=ecom
DB_HOST=127.0.0.1
DB_PORT=3306
# Server Configuration
PUBLIC_HOST=http://localhost
PORT=8080
# JWT Configuration
JWT_SECRET=not-so-secret-now-is-it?
JWT_EXPIRATION_IN_SECONDS=604800 # 7 days in secondstype Config struct {
PublicHost string
Port string
DBUser string
DBPassword string
DBAddress string // Formatted as "host:port"
DBName string
JWTSecret string
JWTExpirationInSeconds int64
}
var Envs = initConfig() // Global configuration instance
// Fallback values if environment variables not set
func initConfig() Config {
return Config{
PublicHost: getEnv("PUBLIC_HOST", "http://localhost"),
Port: getEnv("PORT", "8080"),
DBUser: getEnv("DB_USER", "root"),
DBPassword: getEnv("DB_PASSWORD", "mypassword"),
DBAddress: fmt.Sprintf("%s:%s", getEnv("DB_HOST", "127.0.0.1"), getEnv("DB_PORT", "3306")),
DBName: getEnv("DB_NAME", "ecom"),
JWTSecret: getEnv("JWT_SECRET", "not-so-secret-now-is-it?"),
JWTExpirationInSeconds: getEnvAsInt("JWT_EXPIRATION_IN_SECONDS", 3600*24*7),
}
}- Environment-based: Different settings for dev/staging/prod
- Fallback Values: Sensible defaults if environment variables missing
- Type Conversion: Automatic string to int conversion for numeric values
- Global Access: Configuration available throughout application via
config.Envs
// Success Response
{
"message": "Operation successful",
"data": {...}
}
// Error Response
{
"error": "Detailed error message"
}
// Validation Error
{
"error": "Email is required"
}- 200 OK: Successful GET requests
- 201 Created: Successful POST requests (user registration, product creation)
- 400 Bad Request: Invalid input, validation errors
- 401 Unauthorized: Missing or invalid JWT token
- 404 Not Found: Resource doesn't exist
- 409 Conflict: Duplicate email registration
- 500 Internal Server Error: Database or server errors
func (h *Handler) handleOperation(w http.ResponseWriter, r *http.Request) {
// Early return pattern for errors
if err := validateInput(); err != nil {
utils.WriteError(w, http.StatusBadRequest, err)
return
}
if err := businessLogic(); err != nil {
utils.WriteError(w, http.StatusInternalServerError, err)
return
}
// Success path
utils.WriteJSON(w, http.StatusOK, successData)
}# Development
make run # Start development server on :8080
make build # Build binary to bin/ecom
make test # Run all unit tests
# Database Migrations
make migrate-up # Apply all pending migrations
make migrate-down # Rollback all migrations
make migrate-status # Check current migration version
make migrate-up-one # Apply only one migration
make migrate-down-one # Rollback only one migration
make migrate-goto VERSION=123 # Go to specific migration version
# Migration Troubleshooting
make migrate-fix-dirty VERSION=123 # Fix dirty migration manually
make migrate-fix-auto # Auto-fix dirty migration
make migrate-reset # Reset all migrations (DANGEROUS)
# Database Management
make db-status # Show database tables and structure
make migrate-help # Show all migration commands
# Migration Creation
make migration create_new_table # Create new migration files- Make Changes: Edit code files
- Test Locally: Use
api-tests.httpor curl commands - Run Tests:
make testfor unit tests - Database Changes: Create migration files with
make migration - Apply Migrations:
make migrate-up - Version Control: Commit changes with descriptive messages
# Create new migration
make migration add_new_column
# Apply migrations
make migrate-up
# Check status
make migrate-status
# Rollback if needed (use with caution)
make migrate-down-one- Unit Tests: Located in
services/user/routes_test.go - Integration Tests: Use REST Client with
api-tests.http - Manual Testing: Use curl commands or Postman
- Database Testing: Test with real MySQL database
- ✅ Environment Variables: Sensitive data in .env (not in code)
- ✅ Password Hashing: bcrypt with proper cost factor
- ✅ JWT Security: Strong secret key, reasonable expiration
- ✅ Input Validation: All user inputs validated
- ✅ SQL Injection Protection: Parameterized queries only
- ✅ CORS Configuration: Proper cross-origin settings
- Database Indexing: Indexes on email, foreign keys
- Connection Pooling: MySQL connection pool configured
- JSON Parsing: Efficient request/response handling
- Error Logging: Structured error logging for debugging
- Docker Support: Containerization ready
- Health Endpoint:
/healthfor load balancer checks - Graceful Shutdown: Proper server shutdown handling
- Environment Separation: Dev/staging/prod configurations
- ✅ User registration with email uniqueness validation
- ✅ User authentication with JWT token generation
- ✅ Product CRUD operations (Create, Read by ID, Read All)
- ✅ Shopping cart checkout with order creation
- ✅ JWT middleware for route protection
- ✅ Comprehensive input validation with detailed error messages
- ✅ bcrypt password hashing with secure defaults
- ✅ JWT token authentication with configurable expiration
- ✅ Input validation using go-playground/validator
- ✅ SQL injection protection with parameterized queries
- ✅ Authorization middleware for protected endpoints
- ✅ Proper error handling without information leakage
- ✅ Environment-based configuration with fallbacks
- ✅ Database migrations with rollback support (5 migration files)
- ✅ Comprehensive testing setup with REST Client integration
- ✅ Standardized JSON API responses with proper HTTP status codes
- ✅ Modular service architecture with dependency injection
- ✅ Database connection pooling and proper resource management
- ✅ REST Client integration with comprehensive test scenarios
- ✅ Make commands for development workflow automation
- ✅ Comprehensive documentation with real examples
- ✅ Clear project structure following Go best practices
- ✅ Unit tests for critical user registration functionality
- ✅ Detailed logging for debugging and monitoring
- ✅ Proper separation of concerns (handlers, stores, types)
- ✅ Interface-driven design for testability
- ✅ Generic type conversion utilities for database operations
- ✅ Consistent error handling patterns throughout codebase
- ✅ DECIMAL precision for financial calculations (pricing)
- ✅ Foreign key relationships for data integrity
- Admin Panel: Admin-specific endpoints and permissions
- File Upload: Product image upload functionality
- Email Notifications: User registration and order confirmations
- Payment Integration: Stripe or PayPal integration
- Inventory Management: Stock tracking and alerts
- Search & Filters: Product search and filtering
- Rate Limiting: API rate limiting for production
- Logging: Structured logging with levels
- Metrics: Prometheus metrics for monitoring
- Docker: Container deployment setup
This project demonstrates a solid foundation for a production-ready e-commerce API with modern Go practices and security considerations! 🚀