By Olanrewaju A. Olaboye
| |18 October, 2020
In recent times it has seemed like a magic that social media applications can show you users they believe live around you and you might probably want to connect with. As a developer at one point in time you will need to track location of users and recommend people they may know to establish friend connection/network.
In this tutorial I am going to be showing us exactly how to track last login location of a user, save/update this in our database and use this reservoir of data to recommend users around for each user on the platform.
Prerequisites
Guide
1. git clone https://github.com/WonderfulOlanrewaju/nearby-api-boilerplate.git
2. cd /nearby-api-boilerplate
3. yarn install && yarn add cross-env
The three commands above, command one clones the remote repo into your current working directory. Command two changes directory into the copied folder while command tree install all dependencies needed to run the boilerplate code with signup and login with JWT already done.
create a .env file in the root of your project and add the environment variable
secretKey = yoursecretkeyvalue
Make a sample Register request to your api via postman
Make a sample Login request to your api via postman
Create a file: src/controllers/utils/updateLocation.js
import { User } from "../../models/User.model";
export const UpdateLastLocation = async (ipInfo, userId) => {
let lastLocation = {
type: "Point",
coordinates: ipInfo.ll,
};
let savedUser = await User.findByIdAndUpdate(
userId,
{
ipInfo,
lastLocation,
},
{ new: true }
);
return savedUser;
};
This is a utility function which will be used on any route we want to track location of user to extract their logitude and latitude and save it into some fields in the user object from the DB.
The next step is to update our user model to allow us save ipInfo and lastLocation of user into the DB.
Create a file src/models/User.model.js
import Mongoose from "mongoose";
import autoIncrement from "mongoose-auto-increment";
let { connection, Schema } = Mongoose;
autoIncrement.initialize(connection);
const pointSchema = new Schema({
type: {
type: String,
enum: ["Point"],
required: true,
},
coordinates: {
type: [Number],
required: true,
},
});
const UserSchema = new Schema({
firstName: {
type: String,
min: 2,
default: "",
},
lastName: { type: String, default: "" },
email: { type: String, unique: true },
address: { type: String, default: "" },
password: { type: String, default: "" },
ipInfo: {
ip: { type: String, default: "" },
range: { type: Array, default: [] },
country: { type: String, default: "" },
region: { type: String, default: "" },
eu: { type: String, default: "" },
city: { type: String, default: "" },
ll: { type: Array },
metro: Number,
area: Number,
},
lastLocation: {
type: pointSchema,
default: {
type: "Point",
coordinates: [0, 0],
},
index: "2dsphere",
},
});
UserSchema.plugin(autoIncrement.plugin, {
startAt: 1,
incrementBy: 1,
model: "User",
});
export const User = Mongoose.model("User", UserSchema);
What we did above is to update the user model for new fields that allow us save ipInfo and last location of users into the DB.
What the package allows us to do is discover the longitude and latitude a user made a request from alongside some other details like city, timezone and country based on their ip-address.
yarn add express-ip
Inside add new codes src/app.js
//to the upper part before app.get("/")
import { User } from "./models/User.model";
import expressIP from "express-ip";
app.use(expressIP().getIpInfoMiddleware);
//To the lower part before the last line of code add :
app.get("/nearbyusers", async (req, res) => {
try {
const { ipInfo } = req;
let nearByUsers = await User.find({
lastLocation: {
$nearSphere: {
$geometry: {
type: "Point",
coordinates: ipInfo.ll,
},
$maxDistance: 10000,
},
},
});
if (!nearByUsers || nearByUsers.length === 0) {
res.status(201).json({
message: "No users near you",
nearByUser: [],
});
} else {
res.status(201).json({
message: "Here are users near you",
nearByUsers,
});
}
} catch (err) {
res.status(400).json({
message: `Issues finding nearby users. ${err.message}`,
});
}
});
what we have done is import express-ip package and configure it to be available on all routes of our app. And write a route that basically checks the ipInfo of the person calling it then send them an array of users based on 10Km proximity.
Add a new route to your auth controller for fetching a user detail What we want to use this route for is to update the last location and ip details of where a user made his last request from.
src/controllers/major/auth.controller.js
import { createUser, loginUser } from "../utils/User.util";
import { handleResError, handleResSuccess } from "../utils/response.util";
import JWT from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config();
const { secretKey } = process.env;
export const SignupController = async (req, res) => {
try {
let userDetails = req.body;
let { err, user } = await createUser(userDetails);
if (err) handleResError(res, err, 400);
else {
let { _id, email, isActive } = user;
let options = {
expiresIn: "12h",
issuer: "nearby-hasher",
};
let token = await JWT.sign({ _id, email, isActive }, secretKey, options);
handleResSuccess(res, `Account created!`, token, 201);
}
} catch (err) {
handleResError(res, err, 400);
}
};
export const LoginController = async (req, res) => {
try {
let { err, token } = await loginUser(req.body);
if (err) handleResError(res, err, 400);
else handleResSuccess(res, "login successful", token, 201);
} catch (err) {
handleResError(res, err, 400);
}
};
export const FetchAUserController = async (req, res) => {
try {
console.log(req.decoded);
const { ipInfo } = req;
let id = req.decoded._id;
let updatedUser = await UpdateLastLocation(ipInfo, id);
handleResSuccess(res, "user fetched", updatedUser, 201);
} catch (err) {
handleResError(res, err, 400);
}
};
What is done is basically calling our updateLastLocation function supply it the id and ipInfo of user so this will save the user location details into the db.
Test the user fetch route ensure to add auth token as Authorization header to the request as in the screenshot below. So the ipInfo of the user can be saved in the DB
Update User location on Signup/Login src/controllers/majors/auth.controller.js
import { createUser, loginUser } from "../utils/User.util";
import { handleResError, handleResSuccess } from "../utils/response.util";
import JWT from "jsonwebtoken";
import { User } from "../../models/User.model";
import dotenv from "dotenv";
import { UpdateLastLocation } from "../utils/updateLastLocation";
dotenv.config();
const { secretKey } = process.env;
export const SignupController = async (req, res) => {
try {
let userDetails = req.body;
let ipInfo = { req };
let { err, user } = await createUser(userDetails);
if (err) handleResError(res, err, 400);
else {
let { _id, email, isActive } = user;
await UpdateLastLocation(ipInfo, _id);
let options = {
expiresIn: "12h",
issuer: "nearby-hasher",
};
let token = await JWT.sign({ _id, email, isActive }, secretKey, options);
handleResSuccess(res, `Account created!`, token, 201);
}
} catch (err) {
handleResError(res, err, 400);
}
};
export const LoginController = async (req, res) => {
try {
let ipInfo = { req };
let { err, token } = await loginUser(req.body);
let user = await User.findOne({ email: req.body.email });
await UpdateLastLocation(ipInfo, user._id);
if (err) handleResError(res, err, 400);
else handleResSuccess(res, "login successful", token, 201);
} catch (err) {
handleResError(res, err, 400);
}
};
export const FetchAUserController = async (req, res) => {
try {
console.log(req.decoded);
const { ipInfo } = req;
let id = req.decoded._id;
let updatedUser = await UpdateLastLocation(ipInfo, id);
handleResSuccess(res, "user fetched", updatedUser, 201);
} catch (err) {
handleResError(res, err, 400);
}
};
Add start script to package.json and push app to heroku because the live testing of the nearbyusers route needs to be on a live server
package.json
"start": "node -r esm ./src/server.js"
Push to heroku
You can compare your work with my Final Repo
heroku create
git add .
git commit -m 'heroku push
git push heroku master || git push heroku main
Reason for adding git push heroku main is because master is now renamed as main in recent Github repo creation.
Visit the documentation on Postman Documentation
It will show you the two users you created because they live around you.
This is my first tutorial, kindly comment things to improve on in the comment section.
Comment on dev.to: https://dev.to/boyepanthera/build-a-geocoding-feature-for-finding-users-around-in-nodejs-api-4mg0