I have long suffered with migraines. Currently I track my daily migraines on an Excel spreadsheet, but I am going to create my own app to track them using the MERN stack Node, Express, MongoDB and Mongoose on the server. I will develop the client in React.
- npm init -y to create my package.json file
- create index.js – I import Express to create my server. I also bring in CORS middlesware, which I will need later to handle requests from the client. I also have Morgan for loggin HTTP requests, and body-parser for parsing JSON data. The dotenv module is for reading the .env file.
require("dotenv").config(); // process.env._____
const express = require("express");
const app = express();
const cors = require("cors");
const morgan = require("morgan");
const bodyParser = require("body-parser");
const PORT = process.env.PORT || 3025;
app.get("/", function(req, res) {
res.send("Home route");
});
app.listen(PORT, function() {
console.log("connected on port", PORT);
});
AUTHENTICATIONS
Next I’ll handle authentications. First I’ll create my mongoose model. I’ll use bcrypt for hashing the password.
- I created my User model for mongoose
- auth handler
- auth middleware
- auth route
// MOGOOSE USER MODEL
const mongoose = require("mongoose");
const bycrypt = require("bcrypt");
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
require: true
},
myHeadaches: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "MyHeadaches"
}
]
});
userSchema.pre("save", async function(next) {
try {
if (!this.isModified("password")) {
return next();
}
let hashedPassword = await bycrypt.hash(this.password, 10);
this.password = hashedPassword;
return next();
} catch (err) {
return next(err);
}
});
userSchema.methods.comparePassword = async function(candidatePassword, next) {
try {
let isMatch = await bycrypt.compare(candidatePassword, this.password);
return isMatch;
} catch (err) {
return next(err);
}
};
const User = mongoose.model("User", userSchema);
module.exports = User;
For the Auth handler, I store the mongoose models in db, and load jsonwebtoken into jwt to hash a password for a new user, or to compare a password to the stored hash for an existing user.
// AUTH HANDLER
const db = require("../models");
const jwt = require("jsonwebtoken");
exports.signin = async function(req, res, next) {
//find a user
try {
let user = await db.User.findOne({
email: req.body.email
});
let { id, username } = user;
let isMatch = await user.comparePassword(req.body.passowrd);
if (isMatch) {
let token = jwt.sign(
{
id,
username
},
process.env.SECRET_KEY
);
return res.status(200).json({
id,
username,
token
});
} else {
return next({
status: 400,
message: "Invalid Email/Password."
});
}
} catch (err) {
return next({ status: 400, message: "Invalid Email/Password." });
}
};
exports.signup = async function(req, res, next) {
try {
let user = await db.User.create(req.body);
let { id, username } = user;
let token = jwt.sign(
{
id,
username
},
process.env.SECRET_KEY
);
return res.status(200).json({
id,
username,
token
});
} catch (err) {
if (err.code === 11000) {
err.message = "Sorry, that username and/or email is taken.";
}
return next({
status: 400,
message: err.message
});
}
};
The Auth middleware loginRequired makes sure the user is logged in before they can input new data or access existing data. I use “dotenv” to access the .env file for the SECRET_KEY. I use jsonwebtoken to verify the user.
// AUTH MIDDLEWARE
require("dotenv").load();
const jwt = require("jsonwebtoken");
// make sure user is logged in
exports.loginRequired = function(req, res, next) {
try {
const token = req.headers.authorization.split(" ")[1];
jwt.verify(token, process.env.SECRET_KEY, function(err, decoded) {
if (decoded) {
return next();
} else {
return next({
status: 401,
message: "Please login first."
});
}
});
} catch (err) {
return next({
status: 401,
message: "Please login first"
});
}
};
// make sure we get the correct user - Authorization
exports.ensureCorrectUser = function(req, res, next) {
try {
const token = req.headers.authorization.split(" ")[1];
jwt.verify(token, process.env.SECRET_KEY, function(err, decoded) {
if (decoded && decoded.id === req.params.id) {
return next();
} else {
return next({
status: 401,
message: "Unauthorized"
});
}
});
} catch (err) {
return next({
status: 401,
message: "Unauthorized"
});
}
};
Below I create the Auth routes for signup and signin.
// AUTH ROUTE
const express = require("express");
const router = express.Router();
const { signup, signin } = require("../handlers/auth");
router.post("/signup", signup);
router.post("/signin", signin);
module.exports = router;
Next, I test that the signup route is working, which it seems to be. If I login, it returns with a JSON web token, which is the behavior I’m expecting.
shimo@mvly-BHafner MINGW64 ~/OneDrive - brianhafner.com/Documents/Development/MigraineTracker $ HTTP POST localhost:3025/api/auth/signup username=test password=test [email protected] HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 234 Content-Type: application/json; charset=utf-8 Date: Sun, 02 Sep 2018 19:37:18 GMT ETag: W/"ea-3wrVTn6tT4WS7gQ5iwRAi2MkWjs" X-Powered-By: Express { "id": "5b8c3bee634c83252484666d", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjViOGMzYmVlNjM0YzgzMjUyNDg0NjY2ZCIsInVzZXJuYW1lIjoidGVzdCIsImlhdCI6MTUzNTkxNzAzOH0.BhHCx9xURnczelA1IYyWxPkpwL9Zkwh3V5fFYLV1kHQ", "username": "test" }
ADDING NEW HEADACHE RECORDS
- I created my myHeadaches model for mongoose
- myHeadaches handler
- headaches route
// MYHEADACHES MODEL
const mongoose = require("mongoose");
const User = require("./user");
const myHeadacheSchema = new mongoose.Schema(
{
date: {
type: String,
unique: true
},
painLevel: Number,
comment: String,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
},
{
timestamps: true
}
);
myHeadacheSchema.pre("remove", async function(next) {
try {
let user = await User.findById(this.user);
// remove the id of the headache entry from the list
user.myHeadacheSchema.remove(this.id);
//save the user
await user.save();
return next();
} catch (err) {
return next(err);
}
});
const MyHeadaches = mongoose.model("MyHeadaches", myHeadacheSchema);
module.exports = MyHeadaches;
Handlers – In the myHeadaches handler, I create functions createHeadache, getHeadache, deleteHeadache, and getAllHeadaches to create, delete, and get a list of all headaches.
const db = require("../models");
exports.createHeadache = async function(req, res, next) {
console.log("createHeadache called");
console.log(
"handlers/myCoins.js - createCoin req, res, next -- REQ -",
req.body,
"REQ.PARAMS",
req.params,
"RES",
res.body
);
try {
let headache = await db.MyHeadaches.create({
date: req.body.date,
painLevel: req.body.painLevel,
comment: req.body.comment
});
console.log("myHeadaches/handlers - req.body/headache", headache);
console.log("req.params.id", req.params.id);
let foundUser = await db.User.findById(req.params.id);
console.log("foundUser", foundUser);
foundUser.myHeadaches.push(headache._id);
await foundUser.save();
let foundHeadache = await db.MyHeadaches.findById(headache._id).populate(
"user",
{
username: true
}
);
return res.status(200).json(foundHeadache);
} catch (err) {
return next(err);
}
};
exports.getHeadache = async function(req, res, next) {
try {
let headache = await db.MyHeadaches.find(req.params.coin_id);
return res.status(200).json(headache);
} catch (err) {
return next(err);
}
};
exports.deleteHeadache = async function(req, res, next) {
try {
let foundMyHeadache = await db.MyHeadaches.findById(req.params.headache_id);
await foundMyHeadache.remove();
return res.status(200).json(foundMyHeadache);
} catch (err) {
return next(err);
}
};
exports.getAllHeadaches = async function(req, res, next) {
try {
let allHeadaches = await db.MyHeadaches.find({});
return res.status(200).json(allHeadaches);
} catch (err) {
next(err);
}
};
Routes – Here are my routes for creating, deleting and retrieving all records.
// HEADACHE ROUTES
const express = require("express");
const router = express.Router({ mergeParams: true });
const {
createHeadache,
getHeadache,
deleteHeadache,
getAllHeadaches
} = require("../handlers/myHeadaches");
router.route("/").post(createHeadache);
// Delete route
// prefix - /api/users/:id/headache
router
.route("/:headache_id")
.get(getHeadache)
.delete(deleteHeadache);
router.route("/allHeadaches").get(getAllHeadaches);
module.exports = router;
I tested posting a new entry, and it works.
shimo@mvly-BHafner MINGW64 ~/OneDrive - brianhafner.com/Documents/Development/MigraineTracker
$ http POST localhost:3025/api/users/5b8c3bee634c83252484666d/headaches "Authorization:Bearer eyJhbGciOiJI
UzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjViOGMzYmVlNjM0YzgzMjUyNDg0NjY2ZCIsInVzZXJuYW1lIjoidGVzdCIsImlhdCI6MTUzN
TkxNzAzOH0.BhHCx9xURnczelA1IYyWxPkpwL9Zkwh3V5fFYLV1kHQ" date="09/02/2018" painLevel=6 comment="a bit bette
r today"
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 185
Content-Type: application/json; charset=utf-8
Date: Sun, 02 Sep 2018 22:07:16 GMT
ETag: W/"b9-Tq4q2/EueNlC7JVjM+i90eYwgPU"
X-Powered-By: Express
{
"__v": 0,
"_id": "5b8c5f14a204f02794eb098f",
"comment": "a bit better today",
"createdAt": "2018-09-02T22:07:16.829Z",
"date": "09/02/2018",
"painLevel": 6,
"updatedAt": "2018-09-02T22:07:16.829Z"
}
And deleting a post also works.
// DELETE A POST
shimo@mvly-BHafner MINGW64 ~/OneDrive - brianhafner.com/Documents/Development/MigraineTracker
$ http DELETE localhost:3025/api/users/5b8c3bee634c83252484666d/headaches/5b8c77d6ab694d4e343fbee4 "Author
ization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjViOGMzYmVlNjM0YzgzMjUyNDg0NjY2ZCIsInVzZXJuYW
1lIjoidGVzdCIsImlhdCI6MTUzNTkxNzAzOH0.BhHCx9xURnczelA1IYyWxPkpwL9Zkwh3V5fFYLV1kHQ"
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 220
Content-Type: application/json; charset=utf-8
Date: Sun, 02 Sep 2018 23:54:19 GMT
ETag: W/"dc-UXAfqMSV2z4Mv2dP5sxlrnaYAF0"
X-Powered-By: Express
{
"__v": 0,
"_id": "5b8c77d6ab694d4e343fbee4",
"comment": "yet another comment",
"createdAt": "2018-09-02T23:52:54.946Z",
"date": "08/31/2018",
"painLevel": 9,
"updatedAt": "2018-09-02T23:52:54.946Z",
"user": "5b8c3bee634c83252484666d"
}
Now I’m ready to move onto the frontend.