Transport Project Doc

Ini adalah dokumentasi untuk Aplikasi Transport

Tue May 26 2026
3377 words · 27 minutes
Documentation No tags assigned

Dokumentasi Project — Sankyu Transport Management System

Dokumentasi lengkap untuk developer: tech stack, arsitektur, development, deployment, troubleshooting, dan coding conventions.


Daftar Isi


1. Tech Stack

Backend — node_backend/

KategoriTeknologiVersi
RuntimeNode.js
FrameworkExpress.js^4.18
BahasaJavaScript (CommonJS)
Database UtamaMySQLvia mysql2 ^3.9
Database SekunderMongoDBvia mongoose ^9.1 (legacy)
AutentikasiJWTjsonwebtoken ^9.0
Upload FileMulter^2.0
Excel Import/Exportxlsx ^0.18, exceljs ^4.4
Migrasi DBdbmate^2.32 (dev)
GPS IntegrationWialon API
Reverse GeocodingGeoapify API

Dependency Utama Backend

{
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"exceljs": "^4.4.0",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^9.1.4",
"multer": "^2.0.2",
"mysql2": "^3.9.7",
"xlsx": "^0.18.5"
}

Frontend — tailadmin-vuejs-1.0.0/

KategoriTeknologiVersi
FrameworkVue 3^3.5
BahasaTypeScript~5.7
Build ToolVite^6.0
CSS FrameworkTailwind CSS^4.0
UI TemplateTailAdmin Vue Pro2.0.1
RouterVue Router^4.5
MapsLeaflet + MarkerCluster^1.9
IconsLucide Vue Next, Heroicons
ChartsApexCharts (vue3-apexcharts)^1.8
Date PickerVue Datepicker, Flatpickr
LintingESLint + Prettier
Type Checkvue-tsc^2.2

Dependency Utama Frontend

{
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"tailwindcss": "^4.0.0",
"apexcharts": "^4.4.0",
"lucide-vue-next": "^0.474.0"
}

Documentation — docs/

KategoriTeknologi
Static Site GeneratorVitePress ^1.0
Port Development5174

External Services

ServiceFungsiCatatan
WialonGPS tracking, geofence, trip historyToken di backend .env
GeoapifyReverse geocoding (koordinat → alamat)API key di backend .env
MySQLDatabase utama (data operasional)Local atau remote
MongoDBDatabase sekunder (fitur legacy)Opsional

Ports Default

ServicePort
Backend (Express)3000
Frontend (Vite dev)5173
Docs (VitePress)5174
MySQL3306

2. Architecture

Gambaran Umum

Sistem ini menggunakan arsitektur monorepo dengan dua aplikasi utama:

┌─────────────────────┐ HTTP/JSON ┌──────────────────────┐
│ Vue 3 Frontend │ ◄────────────────► │ Express Backend │
│ (SPA + Vite) │ /api/* │ (Node.js) │
└─────────────────────┘ └──────────┬───────────┘
┌──────────┼───────────┐
│ │ │
┌────▼───┐ ┌───▼────┐ ┌───▼────────┐
│ MySQL │ │MongoDB │ │ Wialon API │
│(utama) │ │(legacy)│ │ (GPS) │
└────────┘ └────────┘ └────────────┘

Backend Architecture

Layer Pattern

Backend mengikuti pola Route → Service → Database:

Request
middleware/auth.js ← JWT verification
middleware/rbac.js ← Role-based access control
routes/<domain>.js ← HTTP handling, validation, response
services/<domain>.js ← Business logic, external API calls
db.js (MySQL pool) ← Direct SQL queries via mysql2/promise
models/<model>.js ← Mongoose models (MongoDB, legacy)

Route Registration

Semua route didaftarkan di server.js dengan prefix /api/:

app.use("/api/trucks", truckRouter);
app.use("/api/sales-costs", salesCostRouter);
app.use("/api/wialon", wialonRouter);
// ...

Authentication Flow

  1. User login via POST /api/auth/login
  2. Backend memverifikasi kredensial dan mengembalikan JWT token
  3. Frontend menyimpan token dan mengirimnya di header Authorization: Bearer <token>
  4. Middleware authenticateToken memverifikasi token di setiap request
  5. Middleware restrictCsAccess membatasi akses role CS ke route tertentu

Role-Based Access Control

RoleAkses
adminSemua endpoint
csHanya GET /schedule-pengiriman, GET /auth/me, PUT /auth/me

Database Connection

MySQL menggunakan connection pool (mysql2/promise):

const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME || "trucking",
connectionLimit: 10
});

Query dilakukan langsung tanpa ORM:

const [rows] = await pool.query("SELECT * FROM truck WHERE is_active = 1");

External Integration: Wialon

Backend Wialon API
│ │
├── Login (token) ────────────────►│
│◄──── Session ID ────────────────┤
│ │
├── search_items (units) ─────────►│
│◄──── Unit positions ────────────┤
│ │
├── get_zones_by_unit ────────────►│
│◄──── Geofence membership ──────┤
│ │
├── unit/get_trips ───────────────►│
│◄──── Trip history ─────────────┤
  • Session di-reuse selama TTL belum habis
  • Semua komunikasi Wialon hanya dari backend (token tidak pernah ke frontend)
  • Response Wialon bisa nested — selalu gunakan normalizer

Geofence Tracking (Background Service)

geofenceTrackingService.js
├── Load active Sales Cost candidates
├── Load route-step → geofence mappings
├── Poll Wialon zone membership (interval)
└── If truck in mapped zone & not yet recorded:
└── INSERT into sales_cost_route_history
  • Berjalan otomatis saat server start
  • Interval dikonfigurasi via GEOFENCE_TRACKING_INTERVAL_MS
  • Hanya mencatat first-entry per step (no duplicates)

Frontend Architecture

Component Hierarchy

App.vue
└── Router View
├── views/Master/* ← CRUD pages
├── views/Transaksi/* ← Transaction pages
├── views/Monitoring/* ← GPS & mileage
├── views/DataTransport/* ← Reports
└── views/Auth/* ← Login

API Communication

Frontend berkomunikasi dengan backend melalui:

  1. Development: Vite proxy (/apihttp://127.0.0.1:3000)
  2. Production: Direct ke VITE_API_URL (e.g., https://sankyu-transport.fun)
src/config/api.js
export const API_ORIGIN = import.meta.env.VITE_API_URL || window.location.origin
export const API_BASE = `${API_ORIGIN}/api`

State Management

  • Tidak menggunakan Vuex/Pinia global store
  • State dikelola per-component atau via composables
  • Data dari API di-fetch langsung di views menggunakan service wrappers

Map Architecture (Monitoring)

TruckLocationMap.vue
├── Left Panel: Leaflet Map (markers, clusters)
├── Middle Panel: Vehicle Detail (shown on selection)
└── Right Panel: Fleet List (scrollable, searchable)
  • Auto-refresh setiap 30 detik
  • Reverse geocode hanya untuk truck yang dipilih (bukan semua)
  • Cache di localStorage dengan TTL 24 jam

Data Flow Patterns

Soft Delete Pattern

Trucks dan drivers menggunakan is_active flag:

Active (is_active = 1):
✓ Muncul di dropdown operasional
✓ Muncul di GPS/map views
✓ Bisa digunakan untuk transaksi baru
Inactive (is_active = 0):
✓ Tetap muncul di Master Data (admin)
✓ Tetap muncul di historical records
✗ Tidak muncul di dropdown operasional
✗ Tidak muncul di GPS/map views
✗ Tidak bisa digunakan untuk transaksi baru

Date Handling

⚠️ Penting: MySQL DATE values harus diperlakukan sebagai local date.

Jangan gunakan:

  • new Date('YYYY-MM-DD') (akan di-parse sebagai UTC)
  • toISOString().slice(0, 10) (bisa bergeser 1 hari)

Gunakan:

// Backend: preserve local calendar parts
const [year, month, day] = dateString.split('-');
// Frontend: parse to local Date
new Date(year, month - 1, day);

File Upload Pattern

Frontend (multipart/form-data)
multer middleware (disk storage)
node_backend/upload/<category>/

Kategori upload: doc-data-truck, doc-data-chasis, doc-supir


3. Development Setup

Prerequisites

SoftwareVersi MinimumCatatan
Node.js18+LTS recommended
npm9+Bundled with Node.js
MySQL8.0+Atau MariaDB 10.6+
Git2.30+
MongoDB6.0+Opsional (fitur legacy)

Langkah 1: Clone Repository

Terminal window
git clone <repo-url> transport_v1.04
cd transport_v1.04

Langkah 2: Setup Backend

Install Dependencies

Terminal window
cd node_backend
npm install

Konfigurasi Environment

Terminal window
Copy-Item .env.example .env

Edit node_backend/.env dan isi nilai yang sesuai:

# Database MySQL
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=your_password
DB_NAME=trucking
# Authentication
JWT_SECRET=your_random_secret_string
# Server
PORT=3000
# Wialon GPS (minta ke admin)
WIALON_BASE_URL=https://hst-api.wialon.com/wialon/ajax.html
WIALON_TOKEN=your_wialon_token
WIALON_LOGIN_FLAGS=13
WIALON_SESSION_TTL_MS=2700000
WIALON_TIMEOUT_MS=20000
# Geoapify Reverse Geocoding
GEOAPIFY_API_KEY=your_geoapify_api_key
GEOAPIFY_BASE_URL=https://api.geoapify.com/v1/geocode/reverse
GEOAPIFY_TIMEOUT_MS=6000
# Cache TTL
REVERSE_GEOCODE_CACHE_TTL_MS=86400000
WIALON_MONTHLY_DISTANCE_CACHE_TTL_MS=600000
# Geofence Tracking
GEOFENCE_TRACKING_INTERVAL_MS=60000
DEFAULT_FINISH_GEOFENCE_NAME=Sankyu

💡 Tip: Untuk development lokal tanpa GPS, cukup isi DB_*, JWT_SECRET, dan PORT. Fitur Wialon dan Geoapify akan gagal gracefully tanpa crash.

Setup Database

Perangkat Baru (Database Kosong):

Terminal window
npm run migrate

Perintah ini akan:

  • Membuat database trucking jika belum ada
  • Menjalankan semua migration files di db/migrations/

Database Existing (Sudah Ada Data):

Terminal window
npm run migrate:adopt-existing

Perintah ini akan:

  • Melengkapi schema yang kurang (tabel tracking baru)
  • Menandai semua migration sebagai sudah diterapkan
  • Tidak menghapus data yang sudah ada

Jalankan Backend

Terminal window
npm start

Backend akan berjalan di http://localhost:3000.

Langkah 3: Setup Frontend

Install Dependencies

Terminal window
cd tailadmin-vuejs-1.0.0
npm install

Jalankan Frontend

Terminal window
npm run dev

Frontend akan berjalan di http://localhost:5173.

ℹ️ Info: Vite dev server otomatis mem-proxy request /api/* ke backend di port 3000. Pastikan backend sudah berjalan sebelum mengakses frontend.

Langkah 4: Setup Documentation (Opsional)

Terminal window
cd docs
npm install
npm run dev

Docs akan berjalan di http://localhost:5174.

Verifikasi Setup

Checklist untuk memastikan setup berhasil:

  • Backend berjalan tanpa error di terminal
  • http://localhost:3000/api/auth/login merespons (POST)
  • Frontend bisa diakses di http://localhost:5173
  • Login berhasil dengan kredensial yang valid
  • Dashboard menampilkan data

Development Workflow

Buka 2 terminal terpisah:

Terminal 1 — Backend:

Terminal window
cd node_backend
npm start

Terminal 2 — Frontend:

Terminal window
cd tailadmin-vuejs-1.0.0
npm run dev

Hot Reload

  • Frontend: Vite HMR otomatis reload saat file berubah
  • Backend: Tidak ada hot reload bawaan. Restart manual dengan Ctrl+C lalu npm start

💡 Tip: Untuk auto-restart backend, install nodemon:

Terminal window
npm install -g nodemon
nodemon server.js

Akses dari Device Lain di LAN

Frontend Vite sudah dikonfigurasi listen di 0.0.0.0:5173. Akses dari device lain:

http://<IP-HOST>:5173

Build Check (Frontend)

Sebelum commit, pastikan build tidak error:

Terminal window
cd tailadmin-vuejs-1.0.0
npm run build-only

Linting & Formatting

Terminal window
cd tailadmin-vuejs-1.0.0
npm run lint # ESLint auto-fix
npm run format # Prettier format

4. Database & Migrations

Overview

Sistem menggunakan MySQL sebagai database utama dan MongoDB untuk fitur legacy. Schema MySQL dikelola menggunakan dbmate yang dibungkus dalam custom Node.js scripts.

Struktur File

node_backend/
├── db.js # MySQL connection pool
├── db/
│ ├── migrations/ # SQL migration files (timestamp-ordered)
│ │ ├── 20260401010000_baseline_from_trucking_dump.sql
│ │ ├── 20260401011000_add_tracking_foreign_keys.sql
│ │ ├── 20260401012000_add_area_finish_geofence.sql
│ │ ├── 20260424010000_add_truck_is_active.sql
│ │ └── 20260424011000_add_driver_is_active.sql
│ ├── schema.sql # Generated schema snapshot
│ └── README.md # Migration CLI documentation
├── scripts/
│ ├── run-dbmate.js # dbmate wrapper
│ ├── build-baseline-migration.js
│ ├── adopt-existing-migrations.js
│ └── dump-schema.js
└── models/ # Mongoose models (MongoDB legacy)

Migration Commands

CommandFungsi
npm run migrateJalankan semua pending migrations
npm run migrate:downRollback migration terakhir
npm run migrate:statusLihat status migration
npm run migrate:new -- <name>Buat file migration baru
npm run migrate:dumpGenerate db/schema.sql dari database aktif
npm run migrate:adopt-existingTandai migrations sebagai applied (DB existing)
npm run migrate:baseline:buildBuild baseline migration dari SQL dump

Workflow: Membuat Perubahan Schema Baru

  1. Buat file migration:

    Terminal window
    cd node_backend
    npm run migrate:new -- add_column_xyz
  2. Edit file yang dihasilkan di db/migrations/:

    -- migrate:up
    ALTER TABLE truck ADD COLUMN xyz VARCHAR(100) NULL;
    -- migrate:down
    ALTER TABLE truck DROP COLUMN xyz;
  3. Jalankan migration:

    Terminal window
    npm run migrate
  4. (Opsional) Update schema snapshot:

    Terminal window
    npm run migrate:dump
  5. Commit file migration ke git

Naming Convention Migration

Format: YYYYMMDDHHMMSS_deskripsi_singkat.sql

Contoh:

  • 20260401010000_baseline_from_trucking_dump.sql
  • 20260424010000_add_truck_is_active.sql

Tabel Utama

truck

KolomTipeCatatan
idINT AUTO_INCREMENTPrimary key
no_polisiVARCHARNomor plat
jenis_kendaraanVARCHARTipe kendaraan
wialon_unit_idVARCHAR(64) NULLMapping ke Wialon unit
is_activeTINYINT(1) DEFAULT 1Soft delete flag

driver

KolomTipeCatatan
idINT AUTO_INCREMENTPrimary key
nama_supirVARCHARNama driver
is_activeTINYINT(1) DEFAULT 1Soft delete flag

sales_cost

Tabel transaksi utama yang menghubungkan truck, driver, customer, area, dan data biaya.

area

KolomTipeCatatan
idINT AUTO_INCREMENTPrimary key
kode_areaVARCHARKode area
nama_areaVARCHARGenerated dari route steps
finish_geofence_resource_idVARCHAR NULLWialon resource ID
finish_geofence_zone_idVARCHAR NULLWialon zone ID
finish_geofence_zone_nameVARCHAR NULLNama geofence finish

area_route_step

Menyimpan urutan langkah route per area dengan mapping ke Wialon geofence.

sales_cost_route_history

Menyimpan riwayat kunjungan geofence aktual per Sales Cost delivery.

schema_migrations

Tabel internal dbmate untuk tracking migration yang sudah diterapkan.

Connection Pool

db.js
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME || "trucking",
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});

Penggunaan di Route/Service

const pool = require("../db");
// Query biasa
const [rows] = await pool.query("SELECT * FROM truck WHERE is_active = 1");
// Query dengan parameter (prepared statement)
const [rows] = await pool.query("SELECT * FROM truck WHERE id = ?", [truckId]);
// Insert
const [result] = await pool.query(
"INSERT INTO truck (no_polisi, jenis_kendaraan) VALUES (?, ?)",
[noPolisi, jenisKendaraan]
);

⚠️ Keamanan: Selalu gunakan parameterized queries (? placeholder). Jangan pernah string concatenation untuk values.

MongoDB (Legacy)

MongoDB digunakan untuk beberapa fitur lama. Model didefinisikan di node_backend/models/.

Koneksi MongoDB opsional — jika MONGO_URI tidak diset di .env, server tetap berjalan tanpa MongoDB.

Backup & Restore

Terminal window
# Export schema
npm run migrate:dump
# Full backup
mysqldump -u root -p trucking > backup_trucking.sql
# Restore
mysql -u root -p trucking < backup_trucking.sql
npm run migrate:adopt-existing

Tips Database

  1. Selalu buat migration untuk perubahan schema — jangan edit database langsung
  2. Commit migration files ke git agar semua developer sinkron
  3. Jangan edit migration yang sudah di-apply — buat migration baru untuk koreksi
  4. Test migration down sebelum push untuk memastikan rollback berfungsi
  5. Gunakan is_active flag untuk soft delete, bukan DELETE FROM
  6. Date handling: Simpan sebagai DATE type, parse sebagai local date (bukan UTC)

5. API Reference

Semua endpoint menggunakan prefix /api/ dan berkomunikasi via JSON. Autentikasi menggunakan JWT Bearer token di header Authorization.

Authentication

Login

POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "password123"
}

Response:

{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": { "id": 1, "username": "admin", "level": "admin" }
}

Get Current User

GET /api/auth/me
Authorization: Bearer <token>

Update Profile

PUT /api/auth/me
Authorization: Bearer <token>

Master Data

Trucks

MethodEndpointDeskripsi
GET/api/trucksList trucks (default: active only)
GET/api/trucks?include_inactive=1List semua trucks
GET/api/trucks?status=activeFilter active
GET/api/trucks?status=inactiveFilter inactive
POST/api/trucksCreate truck
PUT/api/trucks/:idUpdate truck
PATCH/api/trucks/:id/statusToggle active/inactive
DELETE/api/trucks/:idHard delete truck

Drivers

MethodEndpointDeskripsi
GET/api/driversList drivers (default: active only)
GET/api/drivers?include_inactive=1List semua drivers
POST/api/driversCreate driver
PUT/api/drivers/:idUpdate driver
PATCH/api/drivers/:id/statusToggle active/inactive
DELETE/api/drivers/:idHard delete driver

Customers

MethodEndpointDeskripsi
GET/api/customersList customers
POST/api/customersCreate customer
PUT/api/customers/:idUpdate customer
DELETE/api/customers/:idDelete customer

Areas

MethodEndpointDeskripsi
GET/api/areasList areas (includes route_steps)
GET/api/areas/:idGet area detail + route config
POST/api/areasCreate area (with kode_area, route_steps)
PUT/api/areas/:idUpdate area + regenerate nama_area
DELETE/api/areas/:idDelete area

Warehouses

MethodEndpointDeskripsi
GET/api/warehousesList warehouses
POST/api/warehousesCreate warehouse
PUT/api/warehouses/:idUpdate warehouse
DELETE/api/warehouses/:idDelete warehouse

Subcontractors

MethodEndpointDeskripsi
GET/api/subcontsList subcontractors
POST/api/subcontsCreate subcontractor
PUT/api/subconts/:idUpdate subcontractor
DELETE/api/subconts/:idDelete subcontractor

Admins

MethodEndpointDeskripsi
GET/api/adminsList admin users
POST/api/adminsCreate admin
PUT/api/admins/:idUpdate admin
DELETE/api/admins/:idDelete admin

Transactions

Sales Cost

MethodEndpointDeskripsi
GET/api/sales-costsList sales costs
GET/api/sales-costs/:idDetail (includes route_steps, route_history)
GET/api/sales-costs/:id/printPrint single SPK
POST/api/sales-costsCreate sales cost
PUT/api/sales-costs/:idUpdate sales cost
DELETE/api/sales-costs/:idDelete sales cost

ℹ️ Info: Sales Cost reject inactive trucks/drivers untuk create dan import. Edit existing record memperbolehkan keep current inactive truck/driver, tapi reject ganti ke inactive lain.

Repairs

MethodEndpointDeskripsi
GET/api/repairsList repairs
POST/api/repairsCreate repair
PUT/api/repairs/:idUpdate repair
DELETE/api/repairs/:idDelete repair

Subcontractor Transactions

MethodEndpointDeskripsi
GET/api/subcontractorList subcontractor transactions
POST/api/subcontractorCreate transaction
PUT/api/subcontractor/:idUpdate transaction
DELETE/api/subcontractor/:idDelete transaction

GPS & Monitoring (Wialon)

Truck Locations

GET /api/wialon/trucks/location
Authorization: Bearer <token>

Response per truck:

{
"id": 1,
"no_polisi": "B 1234 XYZ",
"lat": -6.123456,
"lon": 106.789012,
"speed": 45,
"gps_status": "moving",
"driver_name": "Budi",
"operational_status": "transaksi",
"transaksi": {},
"repair": null,
"last_transaction": {}
}

⚠️ Catatan: Hanya mengembalikan trucks dengan is_active = 1.

Reverse Geocoding

GET /api/wialon/reverse-geocode?lat=-6.123&lon=106.789
Authorization: Bearer <token>
  • Hanya request untuk 1 truck yang dipilih (bukan semua)
  • Cached 24 jam di backend dan frontend localStorage

Monthly Mileage

GET /api/wialon/trucks/monthly-distance?month=2026-01
Authorization: Bearer <token>

Monthly Mileage Export

GET /api/wialon/trucks/monthly-distance/export?month=2026-01
Authorization: Bearer <token>

Returns: .xlsx file

Auto-Map Trucks to Wialon

POST /api/wialon/trucks/auto-map
Authorization: Bearer <token>

Geofence List

GET /api/wialon/geofences
Authorization: Bearer <token>

Other Endpoints

MethodEndpointDeskripsi
GET/api/dashboardDashboard summary data
GET/api/data-trucksData truck reports
GET/api/data-chasisData chasis reports
GET/api/data-supirData driver reports
GET/api/monitoring-kendaraanVehicle monitoring summary
POST/api/master/importImport master data dari Excel
GET/api/master/templateDownload import template
GET/api/notificationsList notifications
GET/api/schedule-pengirimanSchedule pengiriman (accessible by CS)
GET/api/address-bookAddress book

Error Responses

Semua error mengikuti format:

{
"message": "Deskripsi error dalam Bahasa Indonesia"
}
Status CodeArti
400Bad Request — validasi gagal
401Unauthorized — token tidak valid/expired
403Forbidden — role tidak punya akses
404Not Found — resource tidak ditemukan
500Internal Server Error — error di server

Authentication Header

Semua endpoint (kecuali /api/auth/login) memerlukan:

Authorization: Bearer <jwt_token>

6. Deployment

Arsitektur Production

Internet
[Reverse Proxy / Domain]
├── https://sankyu-transport.fun (Frontend)
│ └── Static files (Vite build output)
└── https://sankyu-transport.fun/api/* (Backend)
└── Node.js Express (port 3000)

Prerequisites Production

SoftwareVersi
Node.js18+ LTS
MySQL8.0+
npm9+
Git2.30+

Opsional:

  • MongoDB 6.0+ (jika fitur legacy digunakan)
  • PM2 atau systemd (process manager)
  • Nginx atau Caddy (reverse proxy)

Deploy Backend

1. Clone & Install

Terminal window
git clone <repo-url> /opt/transport
cd /opt/transport/node_backend
npm install --production

2. Konfigurasi Environment

Buat file .env dengan nilai production:

DB_HOST=localhost
DB_PORT=3306
DB_USER=transport_user
DB_PASS=<strong_password>
DB_NAME=trucking
JWT_SECRET=<random_64_char_string>
PORT=3000
WIALON_BASE_URL=https://hst-api.wialon.com/wialon/ajax.html
WIALON_TOKEN=<production_wialon_token>
WIALON_LOGIN_FLAGS=13
WIALON_SESSION_TTL_MS=2700000
WIALON_TIMEOUT_MS=20000
GEOAPIFY_API_KEY=<production_api_key>
GEOAPIFY_BASE_URL=https://api.geoapify.com/v1/geocode/reverse
GEOAPIFY_TIMEOUT_MS=6000
REVERSE_GEOCODE_CACHE_TTL_MS=86400000
WIALON_MONTHLY_DISTANCE_CACHE_TTL_MS=600000
GEOFENCE_TRACKING_INTERVAL_MS=60000
DEFAULT_FINISH_GEOFENCE_NAME=Sankyu

🔒 Keamanan:

  • Gunakan password MySQL yang kuat dan user dedicated (bukan root)
  • Generate JWT_SECRET yang random dan panjang
  • Jangan commit .env ke git
  • Batasi akses file .env hanya untuk user yang menjalankan service

3. Setup Database

Terminal window
npm run migrate

4. Jalankan dengan Process Manager

Menggunakan PM2:

Terminal window
npm install -g pm2
# Start
pm2 start server.js --name transport-backend
# Auto-start on reboot
pm2 startup
pm2 save
# Monitor
pm2 status
pm2 logs transport-backend

Menggunakan systemd:

/etc/systemd/system/transport-backend.service
[Unit]
Description=Transport Backend API
After=network.target mysql.service
[Service]
Type=simple
User=transport
WorkingDirectory=/opt/transport/node_backend
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=10
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Terminal window
sudo systemctl enable transport-backend
sudo systemctl start transport-backend

Deploy Frontend

1. Build

Terminal window
cd /opt/transport/tailadmin-vuejs-1.0.0
npm install
npm run build-only

Output build ada di folder dist/.

ℹ️ Info: npm run build-only skip type-check untuk build lebih cepat. Gunakan npm run build jika ingin type-check sekaligus.

2. Konfigurasi API URL

Pastikan .env.production mengarah ke URL backend yang benar:

VITE_API_URL=https://sankyu-transport.fun

3. Nginx Configuration

server {
listen 80;
server_name sankyu-transport.fun;
# Frontend static files
root /opt/transport/tailadmin-vuejs-1.0.0/dist;
index index.html;
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API ke backend
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# Proxy static uploads
location /img/ {
proxy_pass http://127.0.0.1:3000;
}
location /doc-data-truck/ {
proxy_pass http://127.0.0.1:3000;
}
location /doc-data-chasis/ {
proxy_pass http://127.0.0.1:3000;
}
location /doc-supir/ {
proxy_pass http://127.0.0.1:3000;
}
}

Alternatif: Development Server sebagai Production

Untuk deployment sederhana (internal network):

Terminal window
# Backend
cd node_backend && npm start
# Frontend
cd tailadmin-vuejs-1.0.0 && npm run dev -- --host 0.0.0.0

⚠️ Peringatan: Cara ini tidak direkomendasikan untuk production publik.

Update / Redeploy

Terminal window
# Pull changes
cd /opt/transport
git pull origin main
# Update backend
cd node_backend
npm install
npm run migrate
pm2 restart transport-backend
# Update frontend
cd ../tailadmin-vuejs-1.0.0
npm install
npm run build-only

SSL/HTTPS

Terminal window
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d sankyu-transport.fun

Monitoring Production

Terminal window
# Logs (PM2)
pm2 logs transport-backend
# Logs (systemd)
journalctl -u transport-backend -f
# Health check
curl http://localhost:3000/api/auth/me
# Expect: 401 = server berjalan

Database Backup (Cron)

/etc/cron.d/transport-backup
0 2 * * * transport mysqldump -u transport_user -p'password' trucking > /backup/trucking_$(date +\%Y\%m\%d).sql

Checklist Deployment

  • .env production sudah dikonfigurasi dengan benar
  • Database migration sudah dijalankan
  • Frontend build berhasil tanpa error
  • Backend berjalan dan merespons di port 3000
  • Reverse proxy (Nginx) dikonfigurasi
  • SSL certificate aktif
  • Process manager (PM2/systemd) dikonfigurasi untuk auto-restart
  • Backup database terjadwal
  • Firewall hanya expose port 80/443

7. Troubleshooting

Backend Issues

Server Tidak Bisa Start

ErrorPenyebabSolusi
ECONNREFUSED 127.0.0.1:3306MySQL tidak berjalanStart MySQL service
Access denied for userKredensial salahCek DB_USER dan DB_PASS di .env
Unknown database 'trucking'Database belum dibuatJalankan npm run migrate
JWT_SECRET belum dikonfigurasi.env tidak lengkapTambahkan JWT_SECRET di .env
MONGO_URI belum dikonfigurasiMongoDB tidak disetOpsional — server tetap jalan
EADDRINUSE :::3000Port sudah dipakaiKill proses lain atau ganti PORT

Cek port yang dipakai:

Terminal window
netstat -ano | findstr :3000
taskkill /PID <pid> /F

API Return 401 Unauthorized

Penyebab umum:

  1. Token expired — login ulang
  2. Token tidak dikirim — cek header Authorization: Bearer <token>
  3. JWT_SECRET berbeda antara saat generate dan verify

API Return 403 Forbidden

User dengan role cs mencoba akses endpoint yang tidak diizinkan. Hanya endpoint berikut yang bisa diakses CS:

  • GET /api/schedule-pengiriman
  • GET /api/auth/me
  • PUT /api/auth/me

MySQL Connection Pool Exhausted

Gejala: Request timeout atau Too many connections

Solusi:

  1. Pastikan tidak ada query yang hang
  2. Cek connectionLimit di db.js (default: 10)
  3. Pastikan setiap query menggunakan pool (bukan manual connection)

Migration Gagal

SituasiSolusi
Table already existsGunakan npm run migrate:adopt-existing
Syntax error di SQLPerbaiki file migration, rollback dulu jika perlu
Permission deniedCek user MySQL punya privilege CREATE/ALTER

Frontend Issues

Halaman Blank / White Screen

  1. Buka browser DevTools (F12) → Console
  2. Cek apakah ada JavaScript error
  3. Cek Network tab — apakah API calls gagal

Penyebab umum:

  • Backend belum berjalan → start backend dulu
  • CORS error → seharusnya tidak terjadi dengan Vite proxy
  • Build error → jalankan npm run build-only untuk cek

API Calls Gagal (Network Error)

Development:

  • Pastikan backend berjalan di port 3000
  • Vite proxy otomatis forward /api/* ke http://127.0.0.1:3000

Production:

  • Pastikan VITE_API_URL di .env.production benar
  • Cek Nginx proxy configuration

CSS / UI Tidak Update

  • Hard refresh: Ctrl + Shift + R
  • Clear browser cache
  • Leaflet cluster icons bisa ter-cache

TypeScript Build Error

Jika ada TS error di file yang tidak terkait:

  • Gunakan npm run build-only (skip type-check)
  • File legacy .js di src/services/ mungkin punya implicit any — known issue

Leaflet Map Tidak Muncul

  1. Container div tidak punya height → pastikan parent punya fixed height
  2. Leaflet CSS tidak ter-import → cek import di component
  3. Tile server unreachable → cek koneksi internet

GPS / Wialon Issues

Semua Truck Muncul Offline

  1. Restart backend
  2. Cek log backend untuk error login Wialon
  3. Verifikasi WIALON_TOKEN di .env belum expired
  4. Cek apakah Wialon API accessible dari server

Reverse Geocode Tidak Menampilkan Alamat

Penyebab:

  1. GEOAPIFY_API_KEY tidak valid atau quota habis
  2. Koordinat tidak valid (0,0 atau null)

Solusi:

  • UI fallback ke koordinat mentah
  • Cek Geoapify dashboard untuk usage/quota
  • Clear localStorage jika ingin force re-fetch:
    Object.keys(localStorage)
    .filter(k => k.startsWith('geocode_'))
    .forEach(k => localStorage.removeItem(k));

Geofence History Tetap “Pending”

Penyebab: Wialon resource/get_zones_by_unit mengembalikan nested payload.

Langkah debug:

  1. Cek raw Wialon membership result untuk truck/unit bermasalah
  2. Verifikasi parsed membership map dari fetchUnitsInZonesByResource
  3. Pastikan normalizeZoneMembershipPayload handle nested format: {resourceId: {zoneId: [unitIds]}}
  4. Cek apakah truck benar-benar di dalam polygon geofence di Wialon

Monthly Mileage “Invalid GPS Mapping”

Penyebab: wialon_unit_id di database tidak cocok dengan unit di Wialon.

Solusi:

  1. Cek mapping di Master Truck
  2. Jalankan auto-map ulang: POST /api/wialon/trucks/auto-map
  3. Atau update wialon_unit_id manual

Database Issues

Tanggal Bergeser 1 Hari

Penyebab: MySQL DATE di-parse melalui UTC conversion.

Solusi:

  • Backend: Jangan gunakan new Date(dateString).toISOString().slice(0,10)
  • Frontend: Parse dengan new Date(year, month - 1, day) bukan new Date('YYYY-MM-DD')

Data Tidak Konsisten Setelah Import

  1. Cek log import di backend console
  2. Verifikasi format Excel sesuai template
  3. Pastikan referensi (truck_id, driver_id) valid dan active
  4. Import akan reject inactive trucks/drivers

Network & Infrastructure

CORS Error

  • Development: Pastikan request ke /api/* (bukan full URL ke port 3000)
  • Production: Pastikan Nginx proxy_pass dikonfigurasi untuk semua path

WebSocket / HMR Error (Development)

  • Pastikan port 5173 tidak diblokir firewall
  • Untuk development lokal, comment out hmr config di vite.config.ts

PowerShell Execution Policy

Terminal window
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

Performance Issues

Backend Response Lambat

  1. Cek slow queries: SHOW PROCESSLIST;
  2. Tambahkan index untuk kolom yang sering di-query/filter
  3. Cek Wialon API timeout (increase WIALON_TIMEOUT_MS)
  4. Cek connection pool usage

Frontend Load Lambat

  1. Jalankan production build (bukan dev mode)
  2. Cek bundle size via npm run build-only
  3. Pastikan gambar/assets sudah optimized
  4. Gunakan browser DevTools → Performance tab

8. Coding Conventions

Backend (Node.js / Express)

Bahasa & Module System

  • JavaScript (bukan TypeScript)
  • CommonJS: require() / module.exports
  • Tidak menggunakan ES Modules di backend

Struktur File Route

const express = require("express");
const router = express.Router();
const pool = require("../db");
const { authenticateToken } = require("../middleware/auth");
router.use(authenticateToken);
router.get("/", async (req, res) => {
try {
const [rows] = await pool.query("SELECT * FROM table_name");
res.json(rows);
} catch (error) {
console.error("Error:", error);
res.status(500).json({ message: "Gagal mengambil data" });
}
});
module.exports = router;

Naming Conventions (Backend)

ItemConventionContoh
File routecamelCasesalesCost.js, dataTruck.js
File servicecamelCasewialonService.js
VariablecamelCasetruckId, noPolisi
Database columnsnake_caseis_active, wialon_unit_id
API endpointkebab-case/api/sales-costs, /api/data-trucks
Error messageBahasa Indonesia"Token tidak ditemukan"

Database Query Pattern

// ✅ Parameterized query (aman dari SQL injection)
const [rows] = await pool.query(
"SELECT * FROM truck WHERE id = ? AND is_active = ?",
[id, 1]
);
// ❌ String concatenation (JANGAN)
const [rows] = await pool.query(
`SELECT * FROM truck WHERE id = ${id}`
);

Error Handling Pattern

router.post("/", async (req, res) => {
try {
// ... logic
res.status(201).json({ message: "Berhasil", data: result });
} catch (error) {
console.error("Deskripsi context:", error);
res.status(500).json({ message: "Pesan error user-friendly" });
}
});

Soft Delete Pattern

// Deactivate
router.patch("/:id/status", async (req, res) => {
const { is_active } = req.body;
await pool.query("UPDATE truck SET is_active = ? WHERE id = ?", [is_active, id]);
});
// Default query: hanya active
const [rows] = await pool.query("SELECT * FROM truck WHERE is_active = 1");
// Admin view: semua
const [rows] = await pool.query("SELECT * FROM truck");

Date Handling (Backend)

// ✅ Preserve local date parts
const formatDate = (dateValue) => {
if (!dateValue) return null;
const d = new Date(dateValue);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// ❌ Jangan gunakan (bisa geser 1 hari karena UTC)
const bad = new Date(dateValue).toISOString().slice(0, 10);

Frontend (Vue 3 / TypeScript)

Bahasa & Style

  • TypeScript untuk file baru (.ts, .vue dengan <script setup lang="ts">)
  • File legacy boleh tetap .js sampai ada kebutuhan refactor
  • Vue 3 Composition API dengan <script setup>
  • Tailwind CSS utility classes

Struktur Component (Vue SFC)

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { API_BASE } from '@/config/api'
const props = defineProps<{
truckId: number
}>()
const loading = ref(false)
const data = ref<TruckData | null>(null)
const fetchData = async () => {
loading.value = true
try {
const res = await fetch(`${API_BASE}/trucks/${props.truckId}`)
data.value = await res.json()
} finally {
loading.value = false
}
}
onMounted(() => {
fetchData()
})
</script>
<template>
<div class="p-4">
<!-- template -->
</div>
</template>

Naming Conventions (Frontend)

ItemConventionContoh
Component filePascalCaseTruckLocationMap.vue
View folderPascalCaseviews/Master/, views/Monitoring/
Service filecamelCasetruckLocationService.ts
ComposablecamelCase with use prefixuseAuth.ts
Variable/refcamelCasetruckList, isLoading
CSS classTailwind utilitiesclass="flex items-center gap-2"

Service Pattern

src/services/truckLocationService.ts
import { API_BASE } from '@/config/api'
export const getTruckLocations = async () => {
const token = localStorage.getItem('token')
const res = await fetch(`${API_BASE}/wialon/trucks/location`, {
headers: { Authorization: `Bearer ${token}` }
})
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
}

Path Alias

Gunakan @/ untuk import dari src/:

import { API_BASE } from '@/config/api'
import TruckCard from '@/components/TruckCard.vue'

Tailwind CSS Guidelines

<!-- ✅ Tailwind utilities -->
<div class="flex items-center gap-4 p-4 rounded-lg bg-white shadow-sm">
<!-- ❌ Hindari custom class tanpa alasan kuat -->
<div class="truck-card-wrapper">

Git Conventions

Commit Messages

Format: <type>: <description>

TypePenggunaan
featFitur baru
fixBug fix
refactorRefactoring tanpa perubahan behavior
docsPerubahan dokumentasi
styleFormatting, missing semicolons, dll
choreMaintenance, dependency update

Contoh:

feat: add monthly mileage export to Excel
fix: date shift issue on Sales Cost edit
refactor: extract geofence tracking to service

Branch Naming

feature/add-truck-mileage
fix/date-shift-sales-cost
refactor/wialon-service-cleanup

File Organization Rules

  1. Satu route file per domain — jangan gabung multiple domains
  2. Business logic di services — route hanya handle HTTP
  3. Satu service file per external integration — e.g., wialonService.js
  4. Views by domainviews/Master/, views/Transaksi/
  5. Shared components di components/ — bukan di dalam views/
  6. Migration files tidak boleh diedit setelah di-apply — buat migration baru

Bahasa

ContextBahasa
Code (variable, function, comments)English
UI textBahasa Indonesia
API error messagesBahasa Indonesia
DocumentationBahasa Indonesia (kecuali technical terms)
Git commitsEnglish

Dokumen ini di-generate dari: docs/developer/ folder
Terakhir diperbarui: Mei 2026


Thanks for reading!

Transport Project Doc

Tue May 26 2026
3377 words · 27 minutes
Documentation No tags assigned

© Rifky Awalul Huda | CC BY-NC-SA 4.0