Complete Guide: NCM ( Nepal Can Move) Integration in Node.js & Express | Step-by-Step Tutorial

๐Ÿ“‹ Table of Contents

  1. Introduction
  2. What You’ll Learn
  3. Prerequisites
  4. Understanding the Architecture
  5. Step-by-Step Implementation
  6. Testing Your Integration
  7. Common Issues and Solutions
  8. 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 โ†’ outForDelivery
  • Arrived at {HUB} โ†’ RTO_IN_TRANSIT โ†’ outForDelivery
  • Returned 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! ๐Ÿš€

Leave a Reply

Your email address will not be published. Required fields are marked *

Share via
Copy link