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.