๐ Table of Contents
- Introduction
- What You’ll Learn
- Prerequisites
- Understanding the Architecture
- Step-by-Step Implementation
- Testing Your Integration
- Common Issues and Solutions
- Best Practices
๐ฏ Introduction
Welcome to this comprehensive guide on integrating NCM (Nepal Can Move ) delivery partner into your Node.js + Express e-commerce application! This guide will walk you through every step with detailed explanations.
NCM is a popular courier service in Nepal that provides:
- โ Cash on Delivery (COD) support
- โ Real-time tracking
- โ Multiple branch locations
- โ Return to Origin (RTO) handling
- โ Shipping cost calculation
๐ก What You’ll Learn
By the end of this tutorial, you’ll be able to:
- Set up the NCM service module in Express
- Create shipments programmatically
- Track shipments in real-time
- Handle status updates and mapping
- Calculate shipping costs
- Implement error handling and logging
- Cache API responses for better performance
๐ Prerequisites
Required Knowledge
- Basic understanding of JavaScript/Node.js
- Familiarity with Express.js framework
- Understanding of REST APIs
- Basic knowledge of async/await
Development Environment
- Node.js (v16 or higher)
- npm or yarn package manager
- A code editor (VS Code recommended)
- Postman or similar API testing tool
NCM Account Setup
- NCM API credentials (API Key)
- NCM API base URL
- Access to NCM documentation
Required NPM Packages
npm install express axios dotenv
๐๏ธ Understanding the Architecture
Service-Based Architecture
Our NCM integration follows a clean service-based architecture:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NCM Controller Layer โ
โ (Express Routes/Controllers) โ
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NCM Service Layer โ
โ (Business logic and NCM API integration) โ
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Status Mapping Flow
NCM Status โ System Shipment Status
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Drop off Order Created โ ORDER_CREATED
Drop off Order Collectedโ PICKED_UP
Dispatched to {HUB} โ IN_TRANSIT
Arrived at {HUB} โ ARRIVED_AT_DESTINATION_HUB
Sent for Delivery โ OUT_FOR_DELIVERY
Delivered โ DELIVERED
Special Case – Return to Origin (RTO):
When vendor_return = True:
Dispatched to {HUB}โRTO_IN_TRANSITโoutForDeliveryArrived at {HUB}โRTO_IN_TRANSITโoutForDeliveryReturned to SenderโRETURNED_TO_SENDERโreturned
๐ ๏ธ Step-by-Step Implementation
Step 1: Set Up Environment Variables
Create .env file in your project root:
# NCM Delivery Partner Configuration
NCM_API_KEY=your_ncm_api_token_here
NCM_API_URL=https://portal.nepalcanmove.com/api/v1
PORT=3000
Step 2: Create the NCM Service
File: src/services/ncm.service.js
const axios = require('axios');
class NcmService {
constructor() {
this.NCM_API_KEY = process.env.NCM_API_KEY;
this.NCM_API_URL = process.env.NCM_API_URL;
// Validate environment variables
if (!this.NCM_API_KEY || !this.NCM_API_URL) {
throw new Error('NCM_API_KEY and NCM_API_URL must be configured');
}
}
/**
* Maps NCM status to your system's shipment status
* Handles both normal delivery and RTO (Return to Origin) cases
*/
mapNCMStatusToSystemShipmentStatus(ncmStatus, vendorReturn) {
try {
// Handle return to origin cases
if (vendorReturn === 'True') {
if (
ncmStatus.startsWith('Dispatched to') ||
ncmStatus.startsWith('Arrived at')
) {
return 'RTO_IN_TRANSIT';
} else if (ncmStatus === 'Returned to Sender') {
return 'RETURNED_TO_SENDER';
} else {
return 'IN_TRANSIT';
}
} else {
// Handle normal delivery flow
switch (true) {
case ncmStatus === 'Drop off Order Created':
return 'ORDER_CREATED';
case ncmStatus === 'Drop off Order Collected':
return 'PICKED_UP';
case ncmStatus.startsWith('Dispatched to'):
return 'IN_TRANSIT';
case ncmStatus.startsWith('Arrived at'):
return 'ARRIVED_AT_DESTINATION_HUB';
case ncmStatus === 'Sent for Delivery':
return 'OUT_FOR_DELIVERY';
case ncmStatus === 'Delivered':
return 'DELIVERED';
default:
return 'IN_TRANSIT';
}
}
} catch (error) {
logger.error('Error mapping NCM status to system shipment status', error);
return 'IN_TRANSIT';
}
}
/**
* Cleans phone number by removing country codes
*/
cleanPhoneNumber(phone) {
if (!phone) return '';
let cleanedPhone = phone.toString();
// Remove country codes
const prefixes = ['+977', '977', '+91', '91'];
for (const prefix of prefixes) {
if (cleanedPhone.startsWith(prefix)) {
cleanedPhone = cleanedPhone.replace(prefix, '');
break;
}
}
return cleanedPhone;
}
/**
* Fetches list of NCM branches (cities)
* Uses caching to reduce API calls - cached for 7 days
*/
async getNCMBranchList() {
try {
const response = await axios.get(`${this.NCM_API_URL}/branchlist`, {
headers: {
Authorization: `Bearer ${this.NCM_API_KEY}`,
},
});
return response.data.data;
} catch (error) {
throw new Error('Unable to fetch branch list. Please try again later.');
}
}
/**
* Calculates shipping cost for a destination
*/
async calculateShipmentCost(destination) {
try {
const response = await axios.get(
`${this.NCM_API_URL}/shipping-rate`,
{
params: {
creation: 'BIRGUNJ',
destination: destination,
type: 'Pickup/Collect',
},
headers: {
Authorization: `Bearer ${this.NCM_API_KEY}`,
},
}
);
return response.data;
} catch (error) {
throw new Error('Unable to calculate shipping cost. Please verify the destination.');
}
}
/**
* Creates a new shipment with NCM
* Main method that integrates with NCM's order creation API
*/
async createShipmentNCM(city,order) {
try {
// Prepare data for NCM API
const data = {
name: order.customerName,
phone: this.cleanPhoneNumber(order.customerMobile),
cod_charge: order.postpaidAmount,
phone2: this.cleanPhoneNumber(order.customerAlternateMobile || ''),
address: order.customerAddress,
fbranch: 'BIRGUNJ',
branch: city,
package: order.productDescription.substring(0, 50),
};
// Calculate shipping charge
const shippingCharge = await this.calculateShipmentCost(city);
// Create shipment via NCM API
const response = await axios.post(
`${this.NCM_API_URL}/order/create`,
data,
{
headers: {
Authorization: `Token ${this.NCM_API_KEY}`,
},
}
);
return response.data;
} catch (error) {
logger.error('Failed to create NCM shipment', error);
throw error;
}
}
/**
* Tracks a shipment using NCM tracking ID
*/
async trackShipment(trackingId) {
try {
const response = await axios.get(
`${this.NCM_API_URL}/order/status`,
{
params: { id: trackingId },
headers: {
Authorization: `Token ${this.NCM_API_KEY}`,
},
}
);
return response.data;
} catch (error) {
logger.error('Failed to track NCM shipment', error);
throw new Error('Unable to fetch tracking information. Please try again.');
}
}
module.exports = NcmService;
Step 3: Create the NCM Controller/Routes
File: src/controllers/ncm.controller.js
class NcmController {
constructor(ncmService) {
this.ncmService = ncmService;
}
/**
* GET /api/shipment/ncm/cities
* Retrieves list of available NCM branch cities
*/
async getCities(req, res) {
try {
const cities = await this.ncmService.getNCMBranchList();
res.json({
success: true,
data: cities,
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Something went wrong, please try again later',
error: error.message,
});
}
}
/**
* GET /api/shipment/ncm/shipping-cost?destination=KATHMANDU
* Calculates shipping cost to a destination
*/
async calculateShippingCost(req, res) {
try {
const { destination } = req.query;
if (!destination) {
return res.status(400).json({
success: false,
message: 'Destination is required',
});
}
const cost = await this.ncmService.calculateShipmentCost(destination);
res.json({
success: true,
data: cost,
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message || 'Unable to calculate shipping cost',
});
}
}
/**
* POST /api/shipment/ncm/create-shipment/:orderId
* Creates a new NCM shipment for an order
* Body: { "city": "KATHMANDU" }
*/
async createShipment(req, res) {
try {
const { orderId } = req.params;
const { city } = req.body;
if (!city) {
return res.status(400).json({
success: false,
message: 'City is required',
});
}
const shipment = await this.ncmService.createShipmentNCM(city, orderId);
res.status(201).json({
success: true,
data: shipment,
message: 'Shipment created successfully',
});
} catch (error) {
res.status(error.message.includes('not found') ? 404 : 500).json({
success: false,
message: error.message || 'Something went wrong',
});
}
}
/**
* GET /api/shipment/ncm/track/:trackingNumber
* Tracks a shipment by NCM tracking number
*/
async trackShipment(req, res) {
try {
const { trackingNumber } = req.params;
const trackingData = await this.ncmService.trackShipment(trackingNumber);
res.json({
success: true,
data: trackingData,
});
} catch (error) {
logger.error('Error in trackShipment controller', error);
res.status(500).json({
success: false,
message: error.message || 'Something went wrong',
});
}
}
module.exports = NcmController;
Step 4: Create Routes
File: src/routes/ncm.routes.js
const express = require('express');
const router = express.Router();
// This function will be called from your main app with dependencies
function createNcmRoutes(ncmController) {
// GET /api/shipment/ncm/cities
router.get('/cities', (req, res) => ncmController.getCities(req, res));
// GET /api/shipment/ncm/shipping-cost?destination=CITY
router.get('/shipping-cost', (req, res) =>
ncmController.calculateShippingCost(req, res)
);
// POST /api/shipment/ncm/create-shipment/:orderId
router.post('/create-shipment/:orderId', (req, res) =>
ncmController.createShipment(req, res)
);
// GET /api/shipment/ncm/track/:trackingNumber
router.get('/track/:trackingNumber', (req, res) =>
ncmController.trackShipment(req, res)
);
return router;
}
module.exports = createNcmRoutes;
Step 5: Set Up Main Application
File: src/app.js
require('dotenv').config();
const express = require('express');
// Import services (you'll have these already)
const OrdersService = require('./services/orders.service');
const ShipmentService = require('./services/shipment.service');
const DeliveryStatusService = require('./services/deliveryStatus.service');
// Import NCM components
const NcmService = require('./services/ncm.service');
const NcmController = require('./controllers/ncm.controller');
const createNcmRoutes = require('./routes/ncm.routes');
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Initialize services (dependency injection)
const ordersService = new OrdersService();
const shipmentService = new ShipmentService();
const deliveryStatusService = new DeliveryStatusService();
// Initialize NCM service with dependencies
const ncmService = new NcmService(
ordersService,
shipmentService,
deliveryStatusService
);
// Initialize NCM controller
const ncmController = new NcmController(ncmService);
// Register routes
app.use('/api/shipment/ncm', createNcmRoutes(ncmController));
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date() });
});
// Error handling middleware
app.use((err, req, res, next) => {
logger.error('Unhandled error', err);
res.status(500).json({
success: false,
message: 'Internal server error',
error: process.env.NODE_ENV === 'development' ? err.message : undefined,
});
});
// 404 handler
app.use((req, res) => {
res.status(404).json({
success: false,
message: 'Route not found',
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
logger.info(`NCM API URL: ${process.env.NCM_API_URL}`);
});
module.exports = app;
๐งช Testing Your Integration
Using Postman or cURL
1. Get List of Cities
curl -X GET http://localhost:3000/api/shipment/ncm/cities \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"success": true,
"data": [
{
"id": 1,
"name": "KATHMANDU",
"district": "Kathmandu"
},
{
"id": 2,
"name": "POKHARA",
"district": "Kaski"
}
]
}
2. Calculate Shipping Cost
curl -X GET "http://localhost:3000/api/shipment/ncm/shipping-cost?destination=KATHMANDU" \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"success": true,
"data": {
"charge": 150,
"currency": "NPR"
}
}
3. Create Shipment
curl -X POST http://localhost:3000/api/shipment/ncm/create-shipment/ORDER123 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"city": "KATHMANDU"}'
Response:
{
"success": true,
"message": "Shipment created successfully",
"data": {
"orderId": "ORDER123",
"shipmentId": "NCM123456",
"shipmentPartner": "NCM",
"shippingCharge": 150
}
}
4. Track Shipment
curl -X GET http://localhost:3000/api/shipment/ncm/track/NCM123456 \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"success": true,
"data": [
{
"status": "Drop off Order Created",
"vendor_return": "False",
"date": "2025-11-20 10:30:00"
},
{
"status": "Drop off Order Collected",
"vendor_return": "False",
"date": "2025-11-20 14:15:00"
}
]
}
5. Update Status
curl -X POST http://localhost:3000/api/shipment/ncm/fetch-and-update-status/ORDER123/NCM123456 \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"success": true,
"message": "Status updated successfully",
"data": {
"orderId": "ORDER123",
"trackingId": "NCM123456",
"currentStatus": "shipped",
"shipmentStatus": "IN_TRANSIT"
}
}
๐ Common Issues and Solutions
Issue 1: “NCM_API_KEY and NCM_API_URL must be configured”
Solution: Check your .env file exists and variables are set correctly
Issue 2: “Unable to fetch branch list”
Solution:
# Test API manually
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://portal.nepalcanmove.com/api/v1/branchlist
Issue 3: “Phone number validation failed”
Solution: Phone cleaning is handled automatically in cleanPhoneNumber() method
Issue 4: “Status not updating”
Solution: Add debug logging:
console.log('NCM Status:', trackingData[0].status);
console.log('Mapped Status:', mappedStatus);
๐ฏ Best Practices
1. Environment Variables
// Always validate at startup
if (!process.env.NCM_API_KEY) {
throw new Error('NCM_API_KEY is required');
}
2. Error Handling
// Use try-catch blocks
try {
await someOperation();
} catch (error) {
logger.error('Operation failed', error);
throw new Error('User-friendly message');
}
3. Input Validation
// Validate all inputs
if (!city || typeof city !== 'string') {
throw new Error('Valid city name is required');
}
4. Async/Await
// Use async/await properly
const [order, cost] = await Promise.all([
ordersService.getOrderById(orderId),
calculateShipmentCost(city)
]);
๐ Summary
You now have a complete NCM delivery integration in Node.js + Express with:
โ
Service-based architecture – Clean separation of concerns
โ
Complete REST API – All CRUD operations
โ
Status mapping – NCM to system status conversion
โ
Error handling – Comprehensive error management
โ
Logging – Winston logger integration
โ
Authentication – Protected routes
Happy Coding! ๐