GPS Tracking System With NodeJS

Want to build a simple GPS tracking system with NodeJS? Here’s a quick TLDR and system overview of what you need:

  • A database to store the last known locations.
  • API endpoints to receive GPS coordinates from the users, output all last known locations.
  • Front end apps or user interface. Web, Android, iOS. Does not quite matter so long as the user’s location is sent to the API endpoint.

Also a quick heads up before we start, we will not cover –

  • Map generation.
  • User login.
  • Security checks.

Yep, this tutorial will only cover a barebones working GPS tracking system.

 

CODE DOWNLOAD

  • On GitHub
  • As a zip
  • Or just git clone https://github.com/dev-n-coffee/gps-tracking-nodejs.git

I have released this under the MIT license, feel free to use it in your own project – Personal or commercial. Some form of credits will be nice though. 🙂

 

 

VIDEO TUTORIAL

 

 

SETUP

Download the above and see README.md.

 

PART 1) DATABASE

Let’s start with the database, we will use SQLite… It’s noob, but easy and convenient. Feel free to use MYSQL, Postgres, Mongo, or whatever else in your project. A single movement table is all we need.

  • id Primary key, integer. Use this to tie to your users, vehicles, packages, whatever you want to track.
  • date Last updated timestamp, text, default current timestamp. If you want to keep a history of the user’s movement set both id and date as the composite primary key.
  • lng Longitude, real.
  • lat Latitude, real.

P.S. Just use a simple SQLite manager like SQLite Studio. Not sponsored.

 

 

PART 2) API ENDPOINTS

index.js
// (A) LOAD MODULES & SETTINGS
const sqlite3 = require("sqlite3"),
      path = require("path"),
      express = require("express"),
      port = 8888,
      multer = require("multer"),
      bodyParser = require("body-parser"),
      cors = require("cors");

// (B) CONNECT TO DATABASE
const db = new sqlite3.Database(
  path.join(__dirname, "/gps.db"),
  sqlite3.OPEN_READWRITE,
  (err) => {
    if (err) {
      console.error(err.message);
      process.exit(1);
    } else {
      console.log("Connected to the gps database.");
    }
  }
);

// (C) INIT EXPRESS SERVER
const app = express();
app.use(multer().array());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors()); // this is bad - allows all
// app.use(cors({ origin: ["https://site.com", "https://my-site.com"] }));

// (D1) ROUTES/ENDPOINTS
// (D1) UPDATE GPS POSITION OF USER
app.post("/set", (req, res) => {
  db.run(
    "REPLACE INTO movement (id, lng, lat) VALUES (?, ?, ?)",
    [req.body.id, req.body.lng, req.body.lat],
    (err) => {
      if (err) {
        console.error(err.message);
        res.status(500).json({ error: err.message });
      } else {
        res.json({ message: "ok" });
      }
    }
  );
});

// (D2) GET GPS POSITION OF ALL USERS
app.get("/get", (req, res) => {
  db.all("SELECT * FROM movement", [], (err, rows) => {
    if (err) {
      console.error(err.message);
      res.status(500).json({ error: err.message });
    } else {
      res.json({
        message: "ok",
        data: rows
      });
    }
  });
});

// (D3) DUMMY TRACKING PAGE
app.get("/track", (req, res) => {
  res.sendFile(path.join(__dirname, "/track.html"));
});

// (D4) DUMMY ADMIN PAGE
app.get("/admin", (req, res) => {
  res.sendFile(path.join(__dirname, "/admin.html"));
});

// (E) START EXPRESS SERVER
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Let’s take this apart section by section.

  1. Start by loading all the required modules.
  2. Connect to the database, stop if it fails.
  3. Initialize Express server.
  4. Set the endpoints/routes.
    • /set Users will post id lat lng to this endpoint to update their current location.
    • /get Call this endpoint to get all user locations.
    • /track Dummy tracking page.
    • /admin Dummy admin page.
  5. Start Express server.

 

 

PART 3) FRONT END PAGES

3A) TRACKING PAGE

track.html
// (A) RANDOM ID 1 TO 100
var id = Math.floor(Math.random() * 100) + 1;

// (B) GPS TRACKER
const tracker = () => {
  navigator.geolocation.getCurrentPosition(
    // (B1) ON SUCCESS
    (pos) => {
      // (B1-1) COLLECT DATA
      let data = new FormData();
      data.append("id", id);
      data.append("lat", pos.coords.latitude);
      data.append("lng", pos.coords.longitude);
      console.log("Sending...", pos.coords);

      // (B1-2) SEND TO API ENDPOINT
      fetch("/set", {
        method: "POST",
        body: data
      })
      .then(res => res.json())
      .then(res => {
        console.log(res, new Date().toLocaleTimeString());
        setTimeout(tracker, 3000); // loop every 3 secs
      })
      .catch(err => console.error(err));
    },

    // (B2) ON FAIL
    err => console.error(err),

    // (B3) GPS SETTINGS - OPTIONAL
    {
      enableHighAccuracy: true,
      timeout: 2500,
      maximumAge: 0
    }
  );
};

// (C) GO!
window.addEventListener("load", tracker);

TLDR:

  • (A) Since user login is not a part of this tutorial, we will just generate a random id.
  • (C) Call tracker() on window load.
  • (B) Get the user’s current GPS location.
  • (B1-1) Collect the id, longitude, latitude into a FormData object.
  • (B1-2) Send it to /set via POST.
  • (B1-2) Take note of how it loops itself with a timer – setTimeout(tracker, 3000)

 

 

3B) ADMIN PAGE

admin.html
// (A) GET USER LOCATIONS FROM SERVER
const tracker = () => {
  fetch("/get")
  .then(res => res.json())
  .then(res => {
    console.log(res.data);
    setTimeout(tracker, 3000); // loop every 3 secs
  })
  .catch(err => console.error(err));
};

// (B) GO!
window.addEventListener("load", tracker);

Just calls /get, JSON decode to get the user locations.

 

PART 4) REACT NATIVE APP (BONUS)

For those who have not caught the drift –

  • The API is independent.
  • Front end apps can be made with HTML, Javascript, Java, Python, React, Vue, or whatever else.
  • So long as the front end can communicate with the API, it works.

So here’s another quick bonus example of creating an app using React Native.

 

 

4A) TRACKER

index.tsx
// (A) IMPORTS
import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet } from "react-native";
import { useIsFocused } from "@react-navigation/native";
import * as Location from "expo-location";

// (B) COMPONENT 
export default function Home () {
  // (B1) USE STATE
  const [msg, setMsg] = useState("INITIALIZING");
  const [id, setID] = useState(Math.floor(Math.random() * 100) + 1); // random id from 1 to 100

  // (B2) GET CURRENT GPS LOCATION
  const tracker = async () => {
    // (B2-1) CHECK GPS ENABLED & PERMISSION
    if (!await Location.hasServicesEnabledAsync()) {
      setMsg("Location Services Disabled");
      return;
    }
    let {status} = await Location.requestForegroundPermissionsAsync();
    if (status !== "granted") {
      setMsg("Please grant location permission");
      return;
    }

    // (B2-2) GET GPS COORDS 
    let {coords} = await Location.getCurrentPositionAsync({});

    // (B2-3) FORM DATA
    let data = new FormData();
    data.append("id", id);
    data.append("lat", coords.latitude);
    data.append("lng", coords.longitude);
    console.log("Sending...", id, data);

    // (B2-4) UPLOAD TO SERVER - CHANGE TO YOUR OWN HOST!
    fetch("http://192.168.18.66:8888/set", {
      method: "POST",
      body: data
    }) 
    .then((res) => res.json())
    .then((res) => {
      console.log(res);
      setMsg(`id : ${id} | date : ${new Date().toLocaleTimeString()} | lat : ${coords.latitude} | lng : ${coords.longitude}`);
    })
    .catch((err) => console.error(err));
  };

  // (B3) RUN EVERY 3 SECS
  const isFocused = useIsFocused();
  useEffect(() => {
    if (isFocused) {
      tracker();
      const interval = setInterval(tracker, 3000);
      return () => clearInterval(interval);
    }
  }, [isFocused]);

  // (B4) LAYOUT
  return (
    <View style={styles.container}><Text>{msg}</Text></View>
  );
}

// (C) COSMETIC STYLES - NOT IMPORTANT
const styles = StyleSheet.create({
  container: {
    flex: 1, backgroundColor: "#fff",
    padding: 20, marginTop: 30
  }
});

Not going to explain too much into the bits, but this literally does the same as (3A).

  • (B1) Generate a random id.
  • (B2) Get the user’s current GPS location.
  • (B2-3) Collect the id, longitude, latitude into a FormData object.
  • (B2-4) Send the data to the /set API endpoint.
  • (B3) Rinse and repeat – Loop every 3 seconds.

Now, a mobile app has an edge over web – You can set this to run in the background, users don’t have to keep their device on.

 

 

4B) ADMIN

admin.tsx
// (A) IMPORTS
import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet } from "react-native";
import { useIsFocused } from "@react-navigation/native";

// (B) COMPONENT - DATA ROW
const Row = (data : {
  id: number,
  date: string,
  lat: number,
  lng: number
}) => {
  return (
    <View><Text>
      ID: {data.id} Date: {data.date} Lat: {data.lat} Lng: {data.lng}
    </Text></View>
  );
};

// (C) COMPONENT - "MAIN ADMIN"
export default function Admin () {
  // (C1) USE STATE
  const [data, setData] = useState([
    {id: 0, date: "INITIALIZING", lat: 0, lng: 0}
  ]);

  // (C2) GET DATA FROM SERVER - CHANGE TO YOUR OWN HOST!
  const tracker = () => {
    fetch("http://192.168.18.66:8888/get")
    .then((res) => res.json())
    .then((res) => {
      console.log(res.data);
      setData(res.data);
    })
    .catch((err) => console.error(err));
  };

  // (C3) RUN ON INTERVAL
  const isFocused = useIsFocused();
  useEffect(() => {
    if (isFocused) {
      tracker();
      const interval = setInterval(tracker, 3000);
      return () => clearInterval(interval);
    }
  }, [isFocused]);

  // (C4) LAYOUT
  return (
    <View style={styles.container}>
    {data.map(row => (
      <Row
        key = {row.id}
        id = {row.id}
        date = {row.date}
        lat = {row.lat}
        lng = {row.lng}
      />
    ))}
    </View>
  );
}

// (D) STYLES
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    padding: 20,
    marginTop: 30
  }
});

Well, does the same as (3B).

  • (C2) Get the locations data from /get.
  • (C1, B, C4) Redraw the interface with the retrieved data.
  • (C3) Rinse and repeat.

 

 

THE END

That’s all for this tutorial, but remember that this is only a barebones system. You still have a lot to deal with:

  • User login.
  • Security. How to verify users? Track users? Verify data? If user “teleports” from one location to miles away in a short time, something is wrong.
  • Map – There are quite a number of map services, here’s one on using Mapbox.