---
title: IoT & Tracking
description: Build IoT applications and real-time tracking systems with NoLag.
---

# IoT & Tracking

Build real-time IoT applications and tracking systems with NoLag.

## Overview

NoLag is ideal for IoT applications that require real-time data streaming from devices. Whether you're tracking vehicles, monitoring sensors, or controlling smart devices, NoLag provides the low-latency infrastructure you need.

## App Setup

Rooms never exist implicitly, so create the app, its room, and its actors before any device or dashboard connects. Give each vehicle its own `device` actor and the dashboard its own `user` actor: publishers never receive their own messages, so a dashboard that reused a device's token would never see that device's updates.

```typescript [Setup (server side, once)]
import { NoLagApi } from '@nolag/js-sdk'

const api = new NoLagApi(process.env.NOLAG_API_KEY) // nlg_live_...

// 1. Create the app and declare the topics it allows. The slug that comes back
//    has a random suffix, so read it from the response.
const app = await api.apps.create({
  name: 'Fleet Tracker',
  slug: 'fleet-tracker',
  topics: ['location', 'telemetry'],
})
console.log(app.slug) // e.g. "fleet-tracker-a3f9": this is your APP_SLUG

// 2. Create the room. Room slugs are kept exactly as you supply them.
await api.rooms.create(app.appId, { name: 'Vehicles', slug: 'vehicles' })

// 3. One device actor per vehicle, and a user actor for the dashboard.
//    Each access token is shown once; keep them.
const truck42 = await api.actors.create({ name: 'truck-42', actorType: 'device', externalId: 'truck-42' })
const dashboard = await api.actors.create({ name: 'fleet-dashboard', actorType: 'user' })
console.log(truck42.accessToken, dashboard.accessToken)
```

## Fleet Tracking Example

The dashboard connects with its `user` actor and subscribes to every vehicle's updates in the `vehicles` room.

```typescript [TypeScript]
import { NoLag } from '@nolag/js-sdk'

const APP_SLUG = 'fleet-tracker-a3f9' // the slug returned when you created the app

interface Location {
  vehicleId: string
  latitude: number
  longitude: number
  heading: number
  speed: number
  timestamp: number
}

interface Telemetry {
  vehicleId: string
  fuelLevel: number
  engineStatus: string
  batteryVoltage: number
}

const client = NoLag('dashboard_access_token')
client.on('error', (err) => console.error(err)) // unknown_topic etc. arrive here
await client.connect()

const fleet = client.setApp(APP_SLUG).setRoom('vehicles')

// Subscribe to all vehicle updates
fleet.subscribe('location')
fleet.subscribe('telemetry')

// Handle location updates. Payloads arrive as `unknown`; name the shape you expect.
fleet.on<Location>('location', (data) => {
  updateMapMarker(data.vehicleId, {
    lat: data.latitude,
    lng: data.longitude,
    heading: data.heading,
    speed: data.speed
  })
})

// Handle telemetry data
fleet.on<Telemetry>('telemetry', (data) => {
  updateVehicleStatus(data.vehicleId, {
    fuel: data.fuelLevel,
    engine: data.engineStatus,
    battery: data.batteryVoltage
  })
})
```
```python [Python]
from nolag import NoLag

APP_SLUG = 'fleet-tracker-a3f9'  # the slug returned when you created the app

client = NoLag('dashboard_access_token')
client.on('error', lambda err: print(err))  # unknown_topic etc. arrive here
await client.connect()

fleet = client.set_app(APP_SLUG).set_room('vehicles')

# Subscribe to all vehicle updates
await fleet.subscribe('location')
await fleet.subscribe('telemetry')

# Handle location updates
def on_location(data, meta):
    update_map_marker(data['vehicleId'], {
        'lat': data['latitude'],
        'lng': data['longitude'],
        'heading': data['heading'],
        'speed': data['speed']
    })

# Handle telemetry data
def on_telemetry(data, meta):
    update_vehicle_status(data['vehicleId'], {
        'fuel': data['fuelLevel'],
        'engine': data['engineStatus'],
        'battery': data['batteryVoltage']
    })

fleet.on('location', on_location)
fleet.on('telemetry', on_telemetry)
```
```go [Go]
package main

import (
    "log"

    nolag "github.com/NoLagApp/go-sdk"
)

const appSlug = "fleet-tracker-a3f9" // the slug returned when you created the app

// Numbers in a payload arrive as the msgpack decoder's sized types (int8, uint16,
// int64, float64, ...), never as int, so convert through a type switch rather
// than asserting a single type. A speed of 42 and a speed of 42.5 decode differently.
func num(v any) float64 {
    switch n := v.(type) {
    case float64:
        return n
    case float32:
        return float64(n)
    case int8:
        return float64(n)
    case int16:
        return float64(n)
    case int32:
        return float64(n)
    case int64:
        return float64(n)
    case uint8:
        return float64(n)
    case uint16:
        return float64(n)
    case uint32:
        return float64(n)
    case uint64:
        return float64(n)
    }
    return 0
}

func main() {
    client := nolag.New("dashboard_access_token")
    client.OnError(func(err *nolag.ServerError) {
        log.Println(err) // unknown_topic etc. arrive here
    })
    if err := client.Connect(); err != nil {
        log.Fatal(err)
    }

    fleet := client.SetApp(appSlug).SetRoom("vehicles")

    // Subscribe to all vehicle updates with handlers
    fleet.Subscribe("location", func(data any, meta nolag.MessageMeta) {
        d, _ := data.(map[string]any)
        vehicleID, _ := d["vehicleId"].(string)
        updateMapMarker(vehicleID, MapPosition{
            Lat:     num(d["latitude"]),
            Lng:     num(d["longitude"]),
            Heading: num(d["heading"]),
            Speed:   num(d["speed"]),
        })
    })

    fleet.Subscribe("telemetry", func(data any, meta nolag.MessageMeta) {
        d, _ := data.(map[string]any)
        vehicleID, _ := d["vehicleId"].(string)
        engine, _ := d["engineStatus"].(string)
        updateVehicleStatus(vehicleID, VehicleStatus{
            Fuel:    num(d["fuelLevel"]),
            Engine:  engine,
            Battery: num(d["batteryVoltage"]),
        })
    })

    select {} // keep the process alive
}
```

## Device Publishing

On the IoT device, connect with that device's own actor and publish updates at regular intervals:

```typescript [TypeScript]
import { NoLag } from '@nolag/js-sdk'

const APP_SLUG = 'fleet-tracker-a3f9' // the slug returned when you created the app
const DEVICE_ID = 'truck-42'

// On the IoT device: its own device actor
const client = NoLag('truck_42_access_token')
await client.connect()

const device = client.setApp(APP_SLUG).setRoom('vehicles')

// Publish location updates every 5 seconds
setInterval(() => {
  const location = gps.getLocation()

  device.emit('location', {
    vehicleId: DEVICE_ID,
    latitude: location.lat,
    longitude: location.lng,
    heading: location.heading,
    speed: location.speed,
    timestamp: Date.now()
  })
}, 5000)
```
```python [Python]
import asyncio
import time
from nolag import NoLag

APP_SLUG = 'fleet-tracker-a3f9'  # the slug returned when you created the app
DEVICE_ID = 'truck-42'

# On the IoT device: its own device actor
client = NoLag('truck_42_access_token')
await client.connect()

device = client.set_app(APP_SLUG).set_room('vehicles')

# Publish location updates every 5 seconds
async def publish_location():
    while True:
        location = gps.get_location()

        await device.emit('location', {
            'vehicleId': DEVICE_ID,
            'latitude': location.lat,
            'longitude': location.lng,
            'heading': location.heading,
            'speed': location.speed,
            'timestamp': int(time.time() * 1000)
        })

        await asyncio.sleep(5)
```
```go [Go]
package main

import (
    "log"
    "time"

    nolag "github.com/NoLagApp/go-sdk"
)

const appSlug = "fleet-tracker-a3f9" // the slug returned when you created the app
const deviceID = "truck-42"

func main() {
    // On the IoT device: its own device actor
    client := nolag.New("truck_42_access_token")
    if err := client.Connect(); err != nil {
        log.Fatal(err)
    }

    device := client.SetApp(appSlug).SetRoom("vehicles")

    // Publish location updates every 5 seconds
    ticker := time.NewTicker(5 * time.Second)
    for range ticker.C {
        location := gps.GetLocation()

        device.Emit("location", map[string]any{
            "vehicleId": deviceID,
            "latitude":  location.Lat,
            "longitude": location.Lng,
            "heading":   location.Heading,
            "speed":     location.Speed,
            "timestamp": time.Now().UnixMilli(),
        })
    }
}
```

## Use Cases

- **Fleet management** - Track vehicles, deliveries, and drivers
- **Asset tracking** - Monitor equipment and inventory location
- **Sensor networks** - Collect data from environmental sensors
- **Smart home** - Control and monitor IoT devices
- **Wearables** - Stream health and fitness data

## Best Practices

- Use one `device` actor per device, and a separate actor for anything that needs to see what the devices publish
- Batch telemetry data to reduce message frequency; the broker accepts 50 publishes per second per connection
- QoS 1, the default, acknowledges each publish on the broker hop. It is not an end-to-end guarantee, so deduplicate on a reading's timestamp if a message must not be applied twice. See [Quality of Service](/docs/concepts/qos)
- Reconnection is built in. Every SDK reconnects automatically after a drop (`reconnect` defaults to true) and the broker restores the connection's subscriptions. Tune the cadence for unreliable networks instead of writing your own loop: `reconnectInterval` in JavaScript (milliseconds, with backoff up to 30 seconds), `reconnect_interval` and `max_reconnect_attempts` in Python, `ReconnectInterval` and `MaxReconnectAttempts` in Go (`-1` retries forever). The default is 5 seconds and 10 attempts
- Consider data retention and historical storage needs. NoLag delivers live readings and does not keep a history for you; a [trigger webhook](/docs/concepts/webhooks) receives every publish and can write it to your own store

## Next Steps

- [Quality of Service](/docs/concepts/qos)
- [Device Authentication](/docs/authentication)
- [Webhooks](/docs/concepts/webhooks)
