README
¶
E-Commerce API
The E-Commerce API is a secure backend service that manages orders, products, and user authentication. It includes an inventory management API to coordinate stock, trigger replenishment, and maintain a dashboard of the operational state. Its business goal is to keep product stock above safety thresholds, streamline purchase orders, and sync aggregates for customer ordering.
Features
-
User Authentication:
- Register users with hashed passwords.
- Login functionality with JWT-based authentication.
- Role-based access control (e.g., admin-only routes).
-
Product Management:
- Create, read, update, and delete (CRUD) operations for products.
- Admin-only access for creating, updating, and deleting products.
- Manage product-level reorder thresholds that act as global fallbacks for automatic replenishment.
-
Order Management:
- Place and retrieve user orders.
- Admin-only functionality for updating order status.
-
Inventory & Procurement:
- Manage suppliers, warehouses, and per-location stock levels.
- Automatically trigger purchase orders when warehouse stock drops below thresholds.
- Track purchase order lifecycles, including receiving goods back into inventory.
-
Swagger API Documentation:
- Auto-generated and interactive documentation for easy API testing.
-
Rate Limiting:
- Per-client rate limiting to prevent abuse.
-
CORS Middleware:
- Protects the API from cross-origin requests while allowing specified domains.
Table of Contents
- Installation
- Setup
- API Documentation
- Environment Variables
- Database Schema
- How It Works
- Usage Guide
- License
Installation
Prerequisites
- Go (v1.18+)
- PostgreSQL (v14+)
Setup
-
Clone the repository:
git clone https://github.com/TobiAdeniji94/ecommerce_api.git cd ecommerce_api -
Install dependencies:
go mod tidy -
Set up environment variables by creating a
.envfile:touch .env -
Populate the
.envfile (see Environment Variables). -
Run the project:
go run main.go
Hot Reloading (optional)
For a smoother development loop install Air and let it rebuild on file changes:
go install github.com/cosmtrek/air@latest
air
The bundled .air.toml configuration compiles to ./tmp/server and restarts the API automatically whenever Go files change.
- Access the application:
- Frontend Dashboard:
https://ecommerce-frontend-555w.onrender.com - API Base URL:
https://ecommerce-api-vkui.onrender.com - Swagger Docs:
https://ecommerce-api-vkui.onrender.com/swagger/index.html
- Frontend Dashboard:
Containerized Setup
Spin up PostgreSQL, the Go API, and the frontend dashboard together using Docker Compose:
docker compose up --build
Exposed services:
- API → http://localhost:3001
- Frontend dashboard → http://localhost:3000
- PostgreSQL → localhost:5432 (username/password/database:
ecommerce)
Environment variables such as JWT_SECRET and DB_* are configured in docker-compose.yml. Tweak them before deployment if necessary.
Containerized Development with Hot Reload
To mount the source tree and run the API with Air inside Docker:
docker compose --profile dev up --build db api-dev frontend
The api-dev service watches for file changes and rebuilds automatically, mirroring the local air workflow.
Health Checks
- API:
GET /healthzreturns 200 when the database is reachable. - Database: Compose uses
pg_isreadyto ensure Postgres is accepting connections before the API starts.
Frontend Dashboard
The repository ships with a lightweight dashboard under frontend/ that consumes the REST API.
- Install a simple static file server (for example,
npm install -g serve). - Run the dashboard on port 3000 (permitted by the backend CORS policy):
serve frontend -l 3000 - Visit http://localhost:3000 and provide:
- API base URL (e.g.
http://localhost:3001/api/v1/) - A JWT token (use an admin token to unlock write operations)
- API base URL (e.g.
- Use the interface to inspect product stock, adjust warehouse inventory, trigger reorder scans, and update purchase orders.
API Documentation
The Swagger UI is available at https://ecommerce-api-vkui.onrender.com/swagger/index.html.
User Management
Register a New User
- Method:
POST - Route:
/api/v1/users/register - Description: Register a new user with email and password.
- Access: Public
Request Payload:
{
"email": "user@example.com",
"password": "securepassword"
}
Response:
- Success (200):
{ "message": "User registered successfully", "data": { "user_id": "uuid-1234-5678-91011" } } - Validation Error (400):
{ "errors": [ { "field": "email", "message": "Email is required" }, { "field": "password", "message": "Password is required" } ] }
Login
- Method:
POST - Route:
/api/v1/users/login - Description: Authenticate a user and issue a JWT for session management.
- Access: Public
Request Payload:
{
"email": "user@example.com",
"password": "securepassword"
}
Response:
- Success (200):
{ "message": "Login successful", "data": { "token": "jwt-token", "user_id": "uuid-1234-5678-91011" } } - Invalid Credentials (401):
{ "message": "Invalid email or password" }
Product Management (Admin Privileges Required)
Create a Product
- Method:
POST - Route:
/api/v1/products - Description: Create a new product.
- Access: Admin only
Request Payload:
{
"name": "Wireless Mouse",
"description": "Ergonomic wireless mouse with adjustable DPI",
"price": 19.99,
"stock": 100
}
Response:
- Success (200):
{ "message": "Product created successfully", "data": { "id": "uuid-1234-5678-91011", "name": "Wireless Mouse", "description": "Ergonomic wireless mouse with adjustable DPI", "price": 19.99, "stock": 100, "created_at": "2024-12-25T10:00:00Z" } }
List All Products
- Method:
GET - Route:
/api/v1/products - Description: Retrieve a list of all available products.
- Access: Authenticated users
Response:
- Success (200):
{ "message": "Products retrieved successfully", "data": [ { "id": "uuid-1234-5678-91011", "name": "Wireless Mouse", "description": "Ergonomic wireless mouse with adjustable DPI", "price": 19.99, "stock": 100 } ] }
Get a Product by ID
- Method:
GET - Route:
/api/v1/products/{id} - Description: Retrieve details of a specific product by its ID.
- Access: Authenticated users
Response:
- Success (200):
{ "message": "Product retrieved successfully", "data": { "id": "uuid-1234-5678-91011", "name": "Wireless Mouse", "description": "Ergonomic wireless mouse with adjustable DPI", "price": 19.99, "stock": 100 } }
Update a Product
- Method:
PUT - Route:
/api/v1/products/{id} - Description: Update details of an existing product by its ID.
- Access: Admin only
Request Payload:
{
"name": "Updated Wireless Mouse",
"description": "Updated ergonomic wireless mouse",
"price": 24.99,
"stock": 150
}
Response:
- Success (200):
{ "message": "Product updated successfully", "data": { "id": "uuid-1234-5678-91011", "name": "Updated Wireless Mouse", "description": "Updated ergonomic wireless mouse", "price": 24.99, "stock": 150 } }
Delete a Product
- Method:
DELETE - Route:
/api/v1/products/{id} - Description: Delete a product by its ID.
- Access: Admin only
Response:
- Success (200):
{ "message": "Product deleted successfully" }
Order Management
Place an Order
- Method:
POST - Route:
/api/v1/orders - Description: Place an order for one or more products.
- Access: Authenticated users
Request Payload:
{
"items": [
{
"product_id": "uuid-1234-5678-91011",
"quantity": 2
}
]
}
Response:
- Success (200):
{ "message": "Order placed successfully", "data": { "order_id": "uuid-1234-5678-91011", "status": "Pending", "items": [ { "product_id": "uuid-1234-5678-91011", "quantity": 2 } ] } }
List All Orders for a User
- Method:
GET - Route:
/api/v1/orders - Description: List all orders placed by the authenticated user.
- Access: Authenticated users
Response:
- Success (200):
{ "message": "Orders retrieved successfully", "data": [ { "order_id": "uuid-1234-5678-91011", "status": "Pending", "items": [ { "product_id": "uuid-1234-5678-91011", "quantity": 2 } ] } ] }
Cancel an Order
- Method:
PUT - Route:
/api/v1/orders/{id}/cancel - Description: Cancel an order if it is still in the "Pending" status.
- Access: Authenticated users
Response:
- Success (200):
{ "message": "Order canceled successfully", "data": { "order_id": "uuid-1234-5678-91011", "status": "Canceled" } }
Update Order Status
- Method:
PUT - Route:
/api/v1/orders/{id}/status - Description: Update the status of an order.
- Access: Admin only
Request Payload:
{
"status": "Shipped"
}
Response:
- Success (200):
{ "message": "Order status updated successfully", "data": { "order_id": "uuid-1234-5678-91011", "status": "Shipped" } }
Inventory Management
- List Warehouse Inventory
GET /api/v1/inventory- Optional
warehouse_idandproduct_idquery parameters filter the response. - Response
{ "message": "Inventory retrieved successfully", "data": [ { "warehouse_id": "uuid-warehouse", "product_id": "uuid-product", "quantity_on_hand": 12, "reorder_threshold": 15, "preferred_supplier_id": "uuid-supplier", "warehouse": { "id": "uuid-warehouse", "name": "North Hub" }, "product": { "id": "uuid-product", "name": "Bluetooth Speaker", "reorder_threshold": 20 }, "preferred_supplier": { "id": "uuid-supplier", "name": "Acme Components" } } ] }
- Create or Update Inventory
POST /api/v1/inventory- Admin-only; upserts quantity, thresholds, and preferred suppliers for a warehouse/product pair.
- Request
{ "warehouse_id": "uuid-warehouse", "product_id": "uuid-product", "quantity_on_hand": 12, "reorder_threshold": 15, "preferred_supplier_id": "uuid-supplier" } - Response
{ "message": "Warehouse inventory upserted successfully", "data": { "warehouse_id": "uuid-warehouse", "product_id": "uuid-product", "quantity_on_hand": 12, "reorder_threshold": 15 } }
- Adjust Inventory Levels
POST /api/v1/inventory/{warehouseId}/{productId}/adjust- Admin-only; increments or decrements on-hand quantity.
- Request
{ "quantity_delta": -2 } - Response
{ "message": "Inventory adjusted successfully", "data": { "warehouse_id": "uuid-warehouse", "product_id": "uuid-product", "quantity_on_hand": 10 } }
- Update Reorder Threshold
PUT /api/v1/inventory/{warehouseId}/{productId}/threshold- Admin-only; updates the threshold that triggers replenishment.
- Request
{ "reorder_threshold": 18 }
- Trigger Reorder Scan
POST /api/v1/inventory/reorder-scan- Admin-only; performs an automated scan to create purchase orders when inventory is low. Warehouse-specific thresholds take precedence, but the product-level
reorder_thresholdprovides a global fallback. - Request
{ "dry_run": true } - Response
{ "message": "Reorder scan completed", "data": [ { "warehouse_id": "uuid-warehouse", "product_id": "uuid-product", "action": "dry_run", "quantity_ordered": 8, "supplier_id": "uuid-supplier", "lead_time_hours": 48 } ] }
Warehouse Management
- List Warehouses
GET /api/v1/warehouses- Optional
include_inventory=truequery parameter embeds inventory details and utilisation. - Response
{ "message": "Warehouse(s) retrieved successfully", "data": [ { "id": "uuid-warehouse", "name": "North Hub", "location": "Manchester, UK", "capacity": 200, "current_utilisation": 18 } ] }
- Create Warehouse
POST /api/v1/warehouses(admin only).- Request
{ "name": "South Hub", "location": "London, UK", "capacity": 150 }
- Get Warehouse
GET /api/v1/warehouses/{id}- Response
{ "message": "Warehouse retrieved successfully", "data": { "id": "uuid-warehouse", "name": "North Hub", "capacity": 200, "inventory": [ { "product_id": "uuid-product", "quantity_on_hand": 12, "reorder_threshold": 15 } ] } }
- Update Warehouse
PUT /api/v1/warehouses/{id}(admin only).- Request
{ "name": "North Hub", "location": "Manchester, UK", "capacity": 220 }
- Delete Warehouse
DELETE /api/v1/warehouses/{id}(admin only).
Supplier Management
- List Suppliers
GET /api/v1/suppliers- Response
{ "message": "Supplier(s) retrieved successfully", "data": [ { "id": "uuid-supplier", "name": "Acme Components", "contact_info": "acme@example.com", "default_lead_time_hours": 48 } ] }
- Create Supplier
POST /api/v1/suppliers(admin only).- Request
{ "name": "Widget Wholesale", "contact_info": "widgets@example.com", "default_lead_time_hours": 72 }
- Get Supplier
GET /api/v1/suppliers/{id}- Response
{ "message": "Supplier retrieved successfully", "data": { "id": "uuid-supplier", "name": "Widget Wholesale", "contact_info": "widgets@example.com", "default_lead_time_hours": 72 } }
- Update Supplier
PUT /api/v1/suppliers/{id}(admin only).- Request
{ "name": "Widget Wholesale", "contact_info": "widgets@example.com", "default_lead_time_hours": 60 }
- Delete Supplier
DELETE /api/v1/suppliers/{id}(admin only).
Purchase Order Management
- List Purchase Orders
GET /api/v1/purchase-orders(admin only) with optionalstatus,warehouse_id, andsupplier_idfilters.- Response
{ "message": "Purchase order(s) retrieved successfully", "data": [ { "id": "uuid-po", "product_id": "uuid-product", "warehouse_id": "uuid-warehouse", "supplier_id": "uuid-supplier", "quantity_ordered": 10, "status": "Pending", "expected_arrival_date": "2025-10-16T12:00:00Z" } ] }
- Create Purchase Order
POST /api/v1/purchase-orders(admin only) to manually request stock from a supplier.- Request
{ "product_id": "uuid-product", "warehouse_id": "uuid-warehouse", "supplier_id": "uuid-supplier", "quantity_ordered": 10 }
- Get Purchase Order
GET /api/v1/purchase-orders/{id}(admin only).
- Update Purchase Order Status
PUT /api/v1/purchase-orders/{id}/status(admin only); receiving an order automatically increases warehouse stock and recalculates product availability.- Request
{ "status": "Received" }
Environment Variables
Create a .env file in the root directory with the following variables:
DB_HOST=
DB_USER=
DB_PASSWORD=
DB_NAME=
DB_PORT=
DB_SSLMODE=require
JWT_SECRET=
PORT=3001
DB_SSLMODE defaults to require. Set it to disable when connecting to local/Postgres containers that do not provide TLS.
Database Schema
users Table
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
email |
VARCHAR(255) | Unique user email |
password |
VARCHAR(255) | Hashed password |
role |
VARCHAR(50) | User role (default: user) |
created_at |
TIMESTAMP | Timestamp of creation |
products Table
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
name |
VARCHAR(255) | Product name |
description |
TEXT | Product description |
price |
FLOAT | Price of the product |
stock |
INT | Available stock (aggregated across warehouses) |
reorder_threshold |
INT | Global fallback threshold for automatic reorders |
created_at |
TIMESTAMP | Timestamp of creation |
suppliers Table
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
name |
VARCHAR(255) | Supplier name |
contact_info |
TEXT | Contact details |
default_lead_time_hours |
INT | Default restock lead time |
created_at |
TIMESTAMP | Timestamp of creation |
warehouses Table
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
name |
VARCHAR(255) | Warehouse label |
location |
VARCHAR(255) | Optional address/location |
capacity |
INT | Maximum units stored |
created_at |
TIMESTAMP | Timestamp of creation |
warehouse_inventory Table
| Column | Type | Description |
|---|---|---|
warehouse_id |
UUID | Part of composite primary key, references warehouses.id |
product_id |
UUID | Part of composite primary key, references products.id |
quantity_on_hand |
INT | Units currently stored |
reorder_threshold |
INT | Warehouse-specific threshold (fallback to product if zero) |
preferred_supplier_id |
UUID | Optional supplier override |
created_at |
TIMESTAMP | Timestamp of creation |
orders Table
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
user_id |
UUID | Foreign key to users table |
status |
VARCHAR(50) | Order status (default: Pending) |
created_at |
TIMESTAMP | Timestamp of creation |
order_items Table
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
order_id |
UUID | Foreign key to orders.id |
product_id |
UUID | Foreign key to products.id |
warehouse_id |
UUID | Warehouse that fulfilled the line (nullable) |
quantity |
INT | Units ordered |
purchase_orders Table
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
product_id |
UUID | Product to restock |
warehouse_id |
UUID | Warehouse receiving the stock |
supplier_id |
UUID | Supplier fulfilling the order |
quantity_ordered |
INT | Units requested |
status |
VARCHAR(20) | Pending/Submitted/Received/Cancelled |
order_date |
TIMESTAMP | Time purchase order was created |
expected_arrival_date |
TIMESTAMP | Estimated arrival time |
received_date |
TIMESTAMP (nullable) | When stock was confirmed received |
How It Works
-
User Authentication
Passwords are hashed with bcrypt and JWT tokens carry the user id/role, enabling admin-only routes. -
Product Management
Admins maintain product catalogue data, including the globalreorder_thresholdthat feeds the replenishment engine. Public consumers can list and read products. -
Warehouse & Inventory Tracking
Each warehouse records capacity and per-product on-hand quantities. Warehouse-level thresholds and preferred suppliers override product defaults when present. -
Automatic Reordering & Purchase Orders
POST /inventory/reorder-scancompares stock against effective thresholds, checks remaining capacity, selects the preferred/default supplier, and creates purchase orders (or simulates them withdry_run). Receiving a purchase order (PUT /purchase-orders/{id}/status) restocks the warehouse and recalculates aggregate product stock. -
Customer Orders
Authenticated users place orders; inventory is reserved from the selected warehouse and replenished if an order is cancelled. Admins can adjust order status. -
Operational Guardrails
Per-client rate limiting and CORS configuration protect the API, while/healthzverifies database connectivity for readiness probes.
Usage Guide
-
Run the backend
export JWT_SECRET=local-dev-secret go run main.go -
Seed core data
Usedocs/sample_data.jsonas a reference. Create suppliers, warehouses, and products (includingreorder_threshold) via the provided APIs, then upsert warehouse inventory. When warehouse-level thresholds are omitted or set to0, the product-level threshold drives automatic reorders.sample_data.jsonshould be used to drive a handful of API calls in the order shown so you end up with a fully functional dataset:- Suppliers – POST each supplier to /api/v1/suppliers with the JSON body from the suppliers array.
- Warehouses – POST each warehouse to /api/v1/warehouses.
- Products – POST to /api/v1/products, including the default_supplier_id field; after you create suppliers you can copy their IDs into these payloads.
- Warehouse Inventory – POST to /api/v1/inventory for each entry: supply the warehouse ID, product ID, quantity, and optional preferred_supplier_id.
-
Trigger reordering
CallPOST /api/v1/inventory/reorder-scan(optionally withdry_run=true) to generate purchase orders whenever stock dips below the effective thresholds while respecting warehouse capacity. -
Acknowledge replenishment
When stock arrives, usePUT /api/v1/purchase-orders/{id}/statuswith{"status":"Received"}to add quantities back into the warehouse and recalculate product availability. -
Adjust stock manually
UsePOST /api/v1/inventory/{warehouseId}/{productId}/adjustwith a positive or negativequantity_deltato simulate sales, shrinkage, or corrections.
License
This project is licensed under the MIT License.
Notes
- Ensure proper error handling for invalid inputs and missing parameters.
- All requests requiring authentication must include the
Authorizationheader with the format:{ "Authorization": "Bearer <JWT_TOKEN>" }
Documentation
¶
There is no documentation for this package.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package docs Code generated by swaggo/swag.
|
Package docs Code generated by swaggo/swag. |