An open API service indexing awesome lists of open source software.

https://github.com/tobiadeniji94/ecommerce_api

The E-Commerce API is a backend service designed to manage orders, products, and user authentication for an online store. It includes secure user registration and login features, product management, and order handling, with robust security and performance optimizations.
https://github.com/tobiadeniji94/ecommerce_api

cors ecommerce gin-framework golang postgresql rate-limiting swagger

Last synced: about 1 month ago
JSON representation

The E-Commerce API is a backend service designed to manage orders, products, and user authentication for an online store. It includes secure user registration and login features, product management, and order handling, with robust security and performance optimizations.

Awesome Lists containing this project

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](#installation)
- [Setup](#setup)
- [API Documentation](#api-documentation)
- [Environment Variables](#environment-variables)
- [Database Schema](#database-schema)
- [How It Works](#how-it-works)
- [Usage Guide](#usage-guide)
- [License](#license)

---

## Installation

### Prerequisites

- **Go** (v1.18+)
- **PostgreSQL** (v14+)

### Setup

1. Clone the repository:
```bash
git clone https://github.com/TobiAdeniji94/ecommerce_api.git
cd ecommerce_api
```

2. Install dependencies:
```bash
go mod tidy
```

3. Set up environment variables by creating a `.env` file:
```bash
touch .env
```

4. Populate the `.env` file (see [Environment Variables](#environment-variables)).

5. Run the project:
```bash
go run main.go
```

### Hot Reloading (optional)

For a smoother development loop install [Air](https://github.com/cosmtrek/air) and let it rebuild on file changes:

```bash
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.

6. Access the application:
- Frontend Dashboard: [`https://ecommerce-frontend-555w.onrender.com`](https://ecommerce-frontend-555w.onrender.com)
- API Base URL: [`https://ecommerce-api-vkui.onrender.com`](https://ecommerce-api-vkui.onrender.com)
- Swagger Docs: [`https://ecommerce-api-vkui.onrender.com/swagger/index.html`](https://ecommerce-api-vkui.onrender.com/swagger/index.html)

---

## Containerized Setup

Spin up PostgreSQL, the Go API, and the frontend dashboard together using Docker Compose:

```bash
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:

```bash
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 /healthz` returns 200 when the database is reachable.
- Database: Compose uses `pg_isready` to 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.

1. Install a simple static file server (for example, `npm install -g serve`).
2. Run the dashboard on port 3000 (permitted by the backend CORS policy):
```bash
serve frontend -l 3000
```
3. Visit [http://localhost:3000](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)
4. 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`](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**:
```json
{
"email": "user@example.com",
"password": "securepassword"
}
```

#### **Response**:
- **Success (200)**:
```json
{
"message": "User registered successfully",
"data": {
"user_id": "uuid-1234-5678-91011"
}
}
```
- **Validation Error (400)**:
```json
{
"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**:
```json
{
"email": "user@example.com",
"password": "securepassword"
}
```

#### **Response**:
- **Success (200)**:
```json
{
"message": "Login successful",
"data": {
"token": "jwt-token",
"user_id": "uuid-1234-5678-91011"
}
}
```
- **Invalid Credentials (401)**:
```json
{
"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**:
```json
{
"name": "Wireless Mouse",
"description": "Ergonomic wireless mouse with adjustable DPI",
"price": 19.99,
"stock": 100
}
```

#### **Response**:
- **Success (200)**:
```json
{
"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)**:
```json
{
"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)**:
```json
{
"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**:
```json
{
"name": "Updated Wireless Mouse",
"description": "Updated ergonomic wireless mouse",
"price": 24.99,
"stock": 150
}
```

#### **Response**:
- **Success (200)**:
```json
{
"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)**:
```json
{
"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**:
```json
{
"items": [
{
"product_id": "uuid-1234-5678-91011",
"quantity": 2
}
]
}
```

#### **Response**:
- **Success (200)**:
```json
{
"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)**:
```json
{
"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)**:
```json
{
"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**:
```json
{
"status": "Shipped"
}
```

#### **Response**:
- **Success (200)**:
```json
{
"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_id` and `product_id` query parameters filter the response.
- **Response**
```json
{
"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**
```json
{
"warehouse_id": "uuid-warehouse",
"product_id": "uuid-product",
"quantity_on_hand": 12,
"reorder_threshold": 15,
"preferred_supplier_id": "uuid-supplier"
}
```
- **Response**
```json
{
"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**
```json
{ "quantity_delta": -2 }
```
- **Response**
```json
{
"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**
```json
{ "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_threshold` provides a global fallback.
- **Request**
```json
{ "dry_run": true }
```
- **Response**
```json
{
"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=true` query parameter embeds inventory details and utilisation.
- **Response**
```json
{
"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**
```json
{
"name": "South Hub",
"location": "London, UK",
"capacity": 150
}
```
- **Get Warehouse**
- `GET /api/v1/warehouses/{id}`
- **Response**
```json
{
"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**
```json
{
"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**
```json
{
"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**
```json
{
"name": "Widget Wholesale",
"contact_info": "widgets@example.com",
"default_lead_time_hours": 72
}
```
- **Get Supplier**
- `GET /api/v1/suppliers/{id}`
- **Response**
```json
{
"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**
```json
{
"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 optional `status`, `warehouse_id`, and `supplier_id` filters.
- **Response**
```json
{
"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**
```json
{
"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**
```json
{ "status": "Received" }
```
---

## Environment Variables

Create a `.env` file in the root directory with the following variables:

```env
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

1. **User Authentication**
Passwords are hashed with bcrypt and JWT tokens carry the user id/role, enabling admin-only routes.

2. **Product Management**
Admins maintain product catalogue data, including the global `reorder_threshold` that feeds the replenishment engine. Public consumers can list and read products.

3. **Warehouse & Inventory Tracking**
Each warehouse records capacity and per-product on-hand quantities. Warehouse-level thresholds and preferred suppliers override product defaults when present.

4. **Automatic Reordering & Purchase Orders**
`POST /inventory/reorder-scan` compares stock against effective thresholds, checks remaining capacity, selects the preferred/default supplier, and creates purchase orders (or simulates them with `dry_run`). Receiving a purchase order (`PUT /purchase-orders/{id}/status`) restocks the warehouse and recalculates aggregate product stock.

5. **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.

6. **Operational Guardrails**
Per-client rate limiting and CORS configuration protect the API, while `/healthz` verifies database connectivity for readiness probes.

---

## Usage Guide

1. **Run the backend**
```bash
export JWT_SECRET=local-dev-secret
go run main.go
```
2. **Seed core data**
Use `docs/sample_data.json` as a reference. Create suppliers, warehouses, and products (including `reorder_threshold`) via the provided APIs, then upsert warehouse inventory. When warehouse-level thresholds are omitted or set to `0`, the product-level threshold drives automatic reorders. `sample_data.json` should 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.

3. **Trigger reordering**
Call `POST /api/v1/inventory/reorder-scan` (optionally with `dry_run=true`) to generate purchase orders whenever stock dips below the effective thresholds while respecting warehouse capacity.
4. **Acknowledge replenishment**
When stock arrives, use `PUT /api/v1/purchase-orders/{id}/status` with `{"status":"Received"}` to add quantities back into the warehouse and recalculate product availability.
5. **Adjust stock manually**
Use `POST /api/v1/inventory/{warehouseId}/{productId}/adjust` with a positive or negative `quantity_delta` to simulate sales, shrinkage, or corrections.

---

## License

This project is licensed under the [MIT License](LICENSE).

---

### **Notes**
- Ensure proper error handling for invalid inputs and missing parameters.
- All requests requiring authentication must include the `Authorization` header with the format:
```json
{
"Authorization": "Bearer "
}
```