The Unified API Middleware provides a robust solution for calculating postage rates by integrating multiple courier services.
At this moment, the API supports domestic postage within Malaysia only (CityLink, Poslaju and J&T). In future updates, it will support international postage for various couriers.
- Prerequisites
- Running the Middleware
- Testing the API
- Debugging and Logs
- Handling Common Issues
- Clean Up
- Cache Mechanism
- API Security
- Additional Resources
-
Environment Setup:
- Python installed (version 3.12 or higher).
- Docker and Docker Compose installed.
- Redis installed locally or running in a container.
-
Clone the Repository:
- GitHub:
git clone https://github.com/lvkmannn/unified_API.git
- GitHub:
-
Install Dependencies:
cd unified_API python -m venv .venv source .venv/bin/activate # On Windows: .venv\\Scripts\\activate pip install -r requirements.txt
- Ensure Redis is running:
- Locally:
sudo systemctl status redis-server
- If it's not running:
sudo systemctl start redis-server
- Locally:
- Start the FastAPI server:
uvicorn app.main:app --reload --port 8080
- Verify the server is running:
- Visit: http://127.0.0.1:8080/
- Build and start containers:
docker-compose up --build
- Verify containers are running:
docker ps
- Verify the server is running:
- Visit: http://127.0.0.1:8080/
Use tools like Postman or cURL to test the API endpoints.
API endpoint: http://127.0.0.1:8080/api/v1/get-rates
Request:
curl -X POST http://127.0.0.1:8080/api/v1/get-rates \
-H "Content-Type: application/json" \
-d '{
"package_type": "parcel",
"origin": {
"postcode": "77400",
"state": "Johor"
},
"destination": {
"postcode": "47130",
"state": "Selangor"
},
"package": {
"weight": 24,
"dimensions": {
"length": 1,
"width": 2,
"height": 3
}
}
}
Response:
{
"data": [
{
"courier": "citylink",
"rate": 53.0
},
{
"courier": "jt",
"rate": 46.64
},
{
"courier": "poslaju",
"rate": 59.91
}
]
}
- Run unit tests:
pytest
- Sample output:
=============================== test session starts ===============================
platform win32 -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0
rootdir: D:\{your_directory}\Project\unified_API
configfile: pytest.ini
plugins: anyio-4.8.0, asyncio-0.25.2, time-machine-2.16.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=None
collected 4 items
app\tests\test_rates.py .... [100%]
=============================== 4 passed in 3.09s ===============================
Logs are generated in the logs/ directory, named by date (YYYY-MM-DD.log).
- View logs in real-time:
tail -f logs/2025-01-18.log
Ensure Redis is running locally or in a container:
redis-cli pingExpected response:
PONG
If containers conflict, remove existing ones:
docker ps -a
docker rm <container-id>
Ensure the payload matches the schema defined in unified.py. Below are the key validation rules:
Validation Rules
-
package_type:- Must be either
"parcel"or"document". - Example:
"package_type": "parcel" - Must be either
-
originanddestination:-
postcode:- Must be a 5-digit number (e.g.,
"47130"). - Example:
"postcode": "47130" - Must be a 5-digit number (e.g.,
-
postcode:- Must be one of the valid Malaysian states:
Johor, Kedah, Kelantan, Malacca, Melaka, Negeri Sembilan, Pahang, Penang, Pulau Pinang, Perak, Perlis, Sabah, Sarawak, Selangor, Terengganu, Kuala Lumpur, Labuan, Putrajaya- Example:
"state": "Selangor"
-
-
package:-
weight:- Must be a positive number.
- Maximum:
30 kg(applies to J&T and Poslaju). - Example:
"weight": 15.5 -
dimensions:- Required. Must be a positive number.
length,width,height— all positive numbers.- Example:
"dimensions": { "length": 1, "width": 2, "height": 3 } -
item_value:- Optional field for J&T insurance cover.
- Rate will vary for J&T only if the user set the item value.
-
Example Valid Payload:
{
"package_type": "parcel",
"origin": {
"postcode": "77400",
"state": "Johor"
},
"destination": {
"postcode": "47130",
"state": "Selangor"
},
"package": {
"weight": 24,
"dimensions": {
"length": 1,
"width": 2,
"height": 3
},
"item_value": 1000 # Optional
}
}
- Stop containers:
docker-compose down
- Deactivate virtual environment:
deactivate
The middleware uses Redis for caching. Here's how it works:
-
Key Generation:
- Cache keys are generated based on the request payload. Example:
parcel:77400-47130:24.0:1x2x3:1000.0 -
Cache Lookup:
- Before making API calls to couriers, the middleware checks if a response is cached.
-
Expiration:
- Cached data expires after a predefined duration (in this project: 60 minutes)
-
Advantages:
- Reduces redundant API calls.
- Improves response times for repeated requests.
Rate Limiting
-
Implementation:
- A rate limiter is integrated using Redis.
- Each client IP is limited to 10 requests per minute.
-
How It Works:
- If the limit is exceeded, the API responds with:
{ "error": "Too many requests", "message": "Please try again later.", "retry_after": 60 } -
Advantages:
- Stops attackers or malicious users from overwhelming your API with excessive requests (e.g., brute force attacks or Denial of Service attacks).
API Versioning
-
Current Version:
- The middleware uses versioning in the URL (
/api/v1). - A rate limiter is integrated using Redis.
- The middleware uses versioning in the URL (
-
Advantages:
- Backward Compatibility: Ensures older clients can continue using the API while you implement secure updates or deprecate outdated methods.
- Enhanced Risk Management: Segregates different versions of the API, allowing you to apply stricter security policies or monitoring to specific versions.
- Compliance with Security Standards: Keeps APIs compliant with modern security standards by rolling out versioned improvements.
-
International Postage Support:
- Expand API functionality to handle international deliveries based on courier.
-
New Features:
- Dynamic rate calculation for insurance coverage.
- Integration with additional couriers.
Contributions are welcome! To contribute:
- Fork the repository.
- Create a new feature branch.
- Submit a pull request with a detailed explanation.
For questions, issues, or suggestions, please open an issue on GitHub or email me at lnhafizramli@gmail.com.
- API Documentation: Access auto-generated docs:
- Swagger UI: http://127.0.0.1:8080/docs
