Jadi Software Engineer di Tengah Gempuran AI, mending Mati aja?
Jadi gini rasanya kuliah di jurusan yang peluang kerjanya mulai saturated.
• 11 min read
A technical breakdown of how I designed and built LaperBang backend architecture, including realtime location tracking, geospatial clustering, vendor matching, and event-driven communication.
You can see a brief explanation about LaperBang HERE, but I will focus on the technical detail on this blog.
LaperBang is built as a real-time geospatial application that combines location tracking, spatial data processing, and event-driven communication.
The main challenge of this project is not only displaying vendors on a map, but creating a system that can handle dynamic vendor movement, process customer demand based on location, detect potential demand areas, and deliver realtime updates efficiently.
The architecture is divided into several main parts:
The user itself consists of 2 roles:
POST /api/v1/auth/register
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | ✓ | - |
name | string | ✓ | - |
email | string | ✓ | - |
password | string | ✓ | - |
role | string | ✗ | vendor | customer |
POST /api/v1/auth/register
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | ✓ | - |
password | string | ✓ | - |
GET /api/v1/auth/me
It will return your detailed information.
PUT /api/v1/auth/profile
Update user profile based on the currently authenticated user’s role.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | ✗ | User name that can be updated by all roles. |
email | string | ✗ | User email that can be updated by all roles. |
password | string | ✗ | New user password. |
name | string | ✗ | User name. |
description | string | ✗ | User profile description. |
vendor_status | string/enum | ✗ | Vendor status. Can only be updated by users with the vendor role. Input example: active, moving, idle, close |
vendor_additional_info | object | ✗ | Additional vendor information. Can only be updated by users with the vendor role. |
customerCustomer can only update:
name
email
password
description
The following fields cannot be updated:
role
vendor_status
vendor_additional_info
vendorVendor can update:
name
email
password
description
vendor_status
vendor_additional_info
The following field cannot be updated:
role{
"name": "Alva",
"email": "alva@example.com",
"password": "new_password"
}
PATCH /api/v1/vendors/status
{
"status": "active | Moving | Idle | Close"
}
Vendor availability rule:
| Status | Can be Followed | Can be Called |
|---|---|---|
| active | ✓ | ✓ |
| moving | ✓ | ✗ |
| idle | ✓ | ✗ |
| close | ✗ | ✗ |
So from the customer side, vendor can be followed or called if it accepts the requirements from the Vendor Status that you can see above.
GET /api/v1/vendors/nearby
GET /api/v1/vendors/nearby?lat=-6.200000&lng=106.816666&radius=5000
| Parameter | Type | Required | Description |
|---|---|---|---|
lat | number | ✓ | Current latitude postition of customer |
lng | number | ✓ | Current longitude position of customer |
radius | number | ✗ | Maxium search radius range in meter, e.g: 100 |
We haven’t decided the maximum radius yet, but we planned to include the radius parameter to minimalize misuse/fake call to a faraway vendors
Follow feature in LaperBang is designed differently from a normal social media follow system.
The purpose of following a vendor is not to create a relationship between users, but to create a realtime tracking subscription between Customer and Vendor.
The Customer first discovers a vendor using a normal HTTP request, then subscribes to the vendor’s realtime channel using Pusher.
Example scenario:
/api/v1/vendors/nearby/api/v1/vendors/:vendor_id/followAfter selecting a vendor, Customer sends: POST /api/v1/vendors/:vendor_id/follow
Example: POST /api/v1/vendors/vendor-uuid/follow
The backend will:
closeDatabase example:
vendor_follows
{
"customer_id": "customer-123",
"vendor_id": "vendor-uuid"
}
This table does not store realtime location.
It only stores:
Customer X wants to receive realtime updates from Vendor Y
After successful follow request, frontend subscribes to:
private-vendor.{vendor_id}
Example:
private-vendor.vendor-uuid
The customer listens to:
vendor.location.updated
vendor.status.updated
POST /api/v1/requests
| Parameter | Type | Required | Description |
|---|---|---|---|
lat | number | ✓ | Latitude postition of customer when requesting |
lng | number | ✓ | Longitude position of customer when requesting |
vendor_id | number | ✗ | Selected vendor that exists on the map |
But what if the there are no other requests within 10m meters beside a current request?
We decided to make it still exists as a standalone request and vendor can see the static request position coordinate just to inform they are being called.
NB:
The DBSCAN Clustering itself is happening when:
How it works?
Vendor with assigned cluster can accept and reject a cluster.
POST /api/v1/clusters/:cluster_id/accept
POST /api/v1/clusters/:cluster_id/reject
POST /api/v1/clusters/cluster-uuid-123/reject
Location sharing feature requires GPS and it costs two things: Battery and Mobile Data.
To make it efficient and doesn’t cost that much resourc
So I propose 2 types of updating location mechanism in this app:
PATCH /api/v1/vendors/location/static
Only use this request to update the vendor location rarely, e.g. vendor on static location like mangkal or idle, updating vendor location on map, etc
| Parameter | Type | Required | Description |
|---|---|---|---|
lat | number | ✓ | Latitude (-90 to 90) |
lng | number | ✓ | Longitude (-90 to 90) |
This request is realtime and updated via Pusher when the vendor is moving or wants to update their location in realtime. Yes I know it costs more resource, but that’s the trade off.
Lets for an example in this route:
PUT /api/v1/vendors/location
| Parameter | Type | Required | Description |
|---|---|---|---|
lat | number | ✓ | Current vendor latitude |
lng | number | ✓ | Current vendor longitude |
live_locations.PUT /api/v1/vendors/locationlive_locationsprivate-vendor.{vendor_id}private-vendor.{vendor_id}Backend checks:
GPS updates can happen very frequently.
Example:
Sending every update is inefficient.
Because of that, backend uses a throttle mechanism.
Example rule:
The backend updates: live_locations
Purpose:
After database update succeeds:
Channel: private-vendor.{vendor_id}
Event: vendor.location.updated
Example payload:
{
"vendor_id": "vendor-uuid-123",
"lat": -6.200000,
"lng": 106.816666
}
Building LaperBang was not only about creating a map-based application, but about designing a system that can handle real-world dynamic interactions between customers and vendors.
The main challenge was combining multiple technologies such as geospatial processing, realtime communication, and event-driven architecture into one system. By using spatial clustering with DBSCAN, LaperBang can detect potential demand areas based on customer requests and connect them with suitable vendors. Meanwhile, realtime communication through Pusher allows customers to track vendor movement and receive updates efficiently without constantly polling the server.
There are still several areas that can be improved, especially around realtime location optimization, scalability, and handling larger amounts of concurrent users. However, this project provided valuable experience in designing backend architecture for location-based systems, from API design, database structure, clustering algorithms, to realtime event management.
Through LaperBang, I learned that building a scalable application is not only about making features work, but also about understanding trade-offs between performance, cost, complexity, and user experience.
Jadi gini rasanya kuliah di jurusan yang peluang kerjanya mulai saturated.
Is it better to die trying, or to live without ever becoming who you wanted to be?
A very brief talk about me