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
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 bothid
anddate
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
// (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.
- Start by loading all the required modules.
- Connect to the database, stop if it fails.
- Initialize Express server.
- Set the endpoints/routes.
/set
Users will postid 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.
- Start Express server.
PART 3) FRONT END PAGES
3A) TRACKING PAGE
// (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
// (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
// (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
// (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.