Skip to content

Latest commit

 

History

History
901 lines (752 loc) · 24.6 KB

File metadata and controls

901 lines (752 loc) · 24.6 KB

Go E-commerce Project - Complete Production-Ready API

Table of Contents

  1. Project Overview
  2. Features Implemented
  3. Quick Start
  4. Project Structure
  5. API Endpoints
  6. Database Schema
  7. Authentication & Security
  8. Testing with REST Client
  9. Understanding Go Patterns
  10. Configuration System
  11. Error Handling
  12. Development Workflow

Project Overview

A production-ready REST API e-commerce backend built with Go, featuring secure authentication, comprehensive product management, and robust error handling.

Tech Stack

  • 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

Features Implemented

✅ User Management

  • 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

✅ Product Management

  • 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

✅ Shopping Cart & Orders

  • Cart Management with user-specific carts
  • Order Processing with order items tracking
  • Database Relationships with proper foreign keys

✅ Security & Authentication

  • JWT Authentication with configurable secrets
  • Password Hashing with bcrypt (cost 10)
  • Input Sanitization and validation
  • Error Response Standardization

✅ Database & Migrations

  • 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

✅ Development Tools

  • REST Client Testing with comprehensive test scenarios
  • Environment Configuration with .env file support
  • Makefile Commands for easy development workflow
  • Comprehensive Documentation with examples

Quick Start

Prerequisites

  • Go 1.19+ installed
  • MySQL server running
  • Git (for version control)

Setup Steps

  1. Clone and Setup
git clone <repository-url>
cd Ecom
go mod tidy
  1. Database Setup
# Create database
mysql -u root -p
CREATE DATABASE ecom;
exit

# Run migrations
make migrate-up
  1. 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
  1. Start Server
make run
# Server starts on http://localhost:8080
  1. Test API
# Health check
curl http://localhost:8080/health

# Or use the REST Client extension with api-tests.http

Project Structure

Ecom/
├── 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

API Endpoints

Authentication Endpoints

POST /api/v1/register

Register a new user account.

Request:

{
    "firstName": "John",
    "lastName": "Doe", 
    "email": "john@example.com",
    "password": "password123"
}

Response (201 Created):

null

Error Response (400 Bad Request):

{
    "error": "invalid payload [FirstName: 'required' tag failed]"
}

Error Response (409 Conflict):

{
    "error": "user with email john@example.com already exists"
}

POST /api/v1/login

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"
}

Product Endpoints

GET /api/v1/products

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 /api/v1/products/{id}

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"
}

POST /api/v1/products (Protected - JWT Required)

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"
}

Cart Endpoints

POST /api/v1/cart/checkout (Protected - JWT Required)

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
}

Database Schema

Users Table

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)
);

Products Table

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
);

Orders Table

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)
);

Order Items Table

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)
);

Migration History

  • 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

Authentication & Security

JWT Implementation

  • 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: WithJWTAuth function for protecting routes

JWT Token Structure

{
  "user_id": "123",
  "exp": 1698765432
}

Protected Routes

  • POST /api/v1/products - Create product (admin functionality)
  • POST /api/v1/cart/checkout - Process checkout

Password Security

  • Hashing: bcrypt with default cost factor
  • Validation: Minimum 6 characters, maximum 50 characters
  • Storage: Only hashed passwords stored in password_hash column

Input Validation

  • JSON Validation: Using go-playground/validator with struct tags
  • Email Format: RFC-compliant email validation
  • Required Fields: Server-side validation with detailed error messages
  • SQL Injection Prevention: Parameterized queries only

Middleware Implementation

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))
    }
}

Testing with REST Client

The project includes api-tests.http for comprehensive API testing:

User Registration Test

### Register User
POST {{baseUrl}}/api/v1/register
Content-Type: application/json

{
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "password": "password123"
}

Authentication Test

### Login User
POST {{baseUrl}}/api/v1/login
Content-Type: application/json

{
    "email": "john.doe@example.com", 
    "password": "password123"
}

Product Management Tests

### 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"
}

Cart Checkout Test

### 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"
}

Error Scenarios Tested

  • ✅ 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

Understanding Go Patterns

Repository Pattern

// 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
}

Handler Pattern

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",
    })
}

Middleware Pattern

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))
    }
}

Configuration System

Environment Variables (.env)

# 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 seconds

Config Structure (config/env.go)

type 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),
    }
}

Configuration Features

  • 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

Error Handling

Standardized Error Responses

// Success Response
{
    "message": "Operation successful",
    "data": {...}
}

// Error Response
{
    "error": "Detailed error message"
}

// Validation Error
{
    "error": "Email is required"
}

HTTP Status Codes Used

  • 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

Error Handling Pattern

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 Workflow

Available Make Commands

# 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

Development Process

  1. Make Changes: Edit code files
  2. Test Locally: Use api-tests.http or curl commands
  3. Run Tests: make test for unit tests
  4. Database Changes: Create migration files with make migration
  5. Apply Migrations: make migrate-up
  6. Version Control: Commit changes with descriptive messages

Migration Workflow

# 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

Testing Workflow

  1. Unit Tests: Located in services/user/routes_test.go
  2. Integration Tests: Use REST Client with api-tests.http
  3. Manual Testing: Use curl commands or Postman
  4. Database Testing: Test with real MySQL database

Production Considerations

Security Checklist

  • 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

Performance Optimizations

  • 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

Deployment Ready

  • Docker Support: Containerization ready
  • Health Endpoint: /health for load balancer checks
  • Graceful Shutdown: Proper server shutdown handling
  • Environment Separation: Dev/staging/prod configurations

Key Achievements

🎯 Complete Feature Implementation

  • ✅ 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

🔒 Security Implementation

  • ✅ 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

🚀 Production-Ready Architecture

  • ✅ 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

🛠 Developer Experience

  • ✅ 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

🏗️ Technical Excellence

  • ✅ 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

Next Steps & Extensions

Potential Enhancements

  1. Admin Panel: Admin-specific endpoints and permissions
  2. File Upload: Product image upload functionality
  3. Email Notifications: User registration and order confirmations
  4. Payment Integration: Stripe or PayPal integration
  5. Inventory Management: Stock tracking and alerts
  6. Search & Filters: Product search and filtering
  7. Rate Limiting: API rate limiting for production
  8. Logging: Structured logging with levels
  9. Metrics: Prometheus metrics for monitoring
  10. Docker: Container deployment setup

This project demonstrates a solid foundation for a production-ready e-commerce API with modern Go practices and security considerations! 🚀