Showing posts with label mongodb. Show all posts
Showing posts with label mongodb. Show all posts

Friday, August 07, 2020

NodeJS JSON API + MongoDB + JWT + ES6 forms

Here is how to create a real-life NodeJS API together with a login form.

Resources:

JavaScript for beginners - learn by doing

Learn Node.js, Express and MongoDB + JWT

We will start with the HTML representing the form as well as its JavaScript functionality:

formLogin.html


  <html>
<body>
<form id="myform">
<div>
<label for="email">Email:</label>
<input type="text" id="email" name="email" />
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
</div>
<div class="button">
<button type="submit" id="loginUser">Send</button>
</div>
</form>

<div id="result"></div>

<script type="text/javascript">
async function fetchData(url = '', data = {}, method, headers = {}) {
const response = await fetch(
url, {
method,
headers: { 'Content-Type': 'application/json', ...headers },
...data && { body: JSON.stringify(data) },
});
return response.json();
}

let form = document.querySelector('#myform');
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
fetchData(
'/user/login',
{ email: this.email.value, password: this.password.value },
'POST'
).then((result) => {
if (result.token) {
// request the url with token
fetchData('/info', null, 'GET', { Bearer: result.token })
.then((result) => { console.log(result); });
return;
}
document.querySelector('#result').innerHTML = `message: ${result.message}`;
})
.catch(error => console.log('error:', error));
})
}
</script>
</body>
</html>



our main node server: index.js

import express from "express";
import mongoose from "mongoose";
import dotenv from "dotenv";

// import the routes
import routes from "./routes/routes.js";

// create an express instance
const app = express();

app.use(express.json())

// setup the middleware routes
routes(app);

// config the database credentials
dotenv.config();

// connect to the database
mongoose.connect(
process.env.DB_CONNECT,
{ useNewUrlParser: true, useUnifiedTopology: true },
() => console.log("connected to mongoDB")
);
// listen for errors
mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection error:'));
// listen on port 3000
app.listen(3000, () => console.log("server is running"));

application routes: routes.js

import { loginUser } from "../controllers/controller.js";
import { info } from "../controllers/info.js"; // the protected route
import { auth } from "../controllers/verifyToken.js"; // middleware for validating the token

import * as path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url); // The absolute URL of the current file.
const __dirname = path.dirname(__filename); // parse just the directory


const routes = app => {
app.route("/user/login").get((req, res) => { res.sendFile('formLogin.html', { root: path.join(__dirname, "../views") }); });
app.route("/user/login").post((req, res) => loginUser(req, res)); // we capture inside req, and res

app.route("/info").get(auth, (req, res) => info(req, res)); // we capture inside req, and res
// and insert the auth middleware to process the token
};
export default routes;


our main controller: controller.js

import mongoose from "mongoose";
mongoose.set("useCreateIndex", true);
import { userSchema } from "../models/user.js";
import jwt from "jsonwebtoken";

const User = mongoose.model("users", userSchema); // users is the name of our collection!
export const addNewUser = (req, res) => {
User.init(() => {
// init() resolves when the indexes have finished building successfully.
// in order for unique check to work

let newUser = new User(req.body); // just creating w/o saving
newUser.password = newUser.encryptPassword(req.body.password);

newUser.save((err, user) => { // now saving
if (err) {
res.json({ 'message': 'duplicate email' });
}
res.json(user);
});
});
};

export const loginUser = (req, res) => {

if (req.body.password == null || req.body.email == null) {
res.status(400).json({ 'message': 'Please provide email / password' });
return;
}

User.init(() => {
User.findOne({ email: req.body.email }, (err, user) => {
if (err) {
res.json(err);
return;
}
if (user == null) {
res.status(400).json({ 'message': 'Non existing user' });
return;
});

// here user is the fetched user
const validPassword = user.validatePassword(req.body.password, user.password);

if (!validPassword) {
res.status(400).json({ 'message': 'Not valid password' });
return;
}

// create and send a token to be able to use it in further requests
const token = jwt.sign({ _id: user._id }, process.env.TOKEN_SECRET);
res.header("auth-token", token) // set the token in the header of the response
.json({ 'token': token }); // display the token
});
});
};



js helper middleware for working with JWT tokens: verifyToken.js

    import jwt from "jsonwebtoken";
export const auth = (req, res, next) => {
const token = req.header("Bearer");
if (!token) return res.status(401).json({'message':'access denied'});
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
if (!verified) res.status(400).message({'message':'Invalid token'});
// continue from the middleware to the next processing middleware :)
next();
};


user database model: user.js

import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';

let userSchema = new mongoose.Schema(
{
email: {
type: String,
requires: "Enter email",
maxlength: 50,
unique: true
},
password: {
type: String,
required: "Enter password",
maxlength: 65
}
},
{
timestamps: true
}
);

userSchema.method({
encryptPassword: (password) => {
return bcrypt.hashSync(password, bcrypt.genSaltSync(5));
},
validatePassword: (pass1, pass2) => {
return bcrypt.compareSync(pass1, pass2);
}
});

export { userSchema };

Congratulations !

Thursday, January 16, 2020

JSON web tokens (JWT) with NodeJS REST API

Here is how to deal with JWT inside REST API routes:
Note: if you want to learn more on JSONWebTokens and REST you can visit this course: Learn Node.js, Express and MongoDB + JWT

 


So let's begin by creating a new directory project:
mkdir auth
setup the project:
npm init -f
Then we will install the following libraries:
for setting up the webserver
npm i express
for the database connection
npm i mongoose
for reading .env files
npm i dotenv
for restarting the nodejs application(webserver)
npm i --save-dev nodemon
for using ES6 syntax inside nodejs
npm i --save-dev @babel/preset-env @babel/core @babel/node
setting up the transpiling inside babel
nano .babelrc
{"presets": ["@babel/preset-env"]}

install eslint
npm install eslint --save-dev
other packages: bcryptjs, jsonwebtoken

to be able to run the code change package.json:
"start": "nodemon --exec babel-node index.js"
start developing from the current directory inside visual studio code:
code .

.env file
DB_CONNECT ="mongodb://127.0.0.1/users"
TOKEN_SECRET = "onetwothreefourfive"

our index.js file:
import express from "express";
import mongoose from "mongoose";
import dotenv from "dotenv";
// import the routes
import routes from "./routes/routes";

// create an express instance
const app = express();

// setup the middleware routes
routes(app);

// config the database credentials
dotenv.config();

// connect to the database
mongoose.connect(
process.env.DB_CONNECT,
{ useNewUrlParser: true, useUnifiedTopology: true },
() => console.log("connected to mongoDB")
);
// listen for errors
mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection error:'));
// listen on port 3000
app.listen(3000, () => console.log("server is running"));


controller.js:
import mongoose from "mongoose";
mongoose.set("useCreateIndex", true);
import { userSchema } from "../models/user.js";
import * as bcrypt from "bcryptjs";
import * as jwt from "jsonwebtoken";

const User = mongoose.model("users", userSchema); // users is the name of our collection!!!
export const addNewUser = (req, res) => {
User.init(() => {
// init() resolves when the indexes have finished building successfully.
// in order for unique check to work
let newUser = new User(req.query); // just creating w/o saving
newUser.password = bcrypt.hashSync(req.query.password, 10); // setting password synchronously
newUser.save((err, user) => { // now saving
if (err) {
res.send(err.message);
}
res.json(user);
});
});
};

export const loginUser = (req, res) => {
User.init(() => {
User.findOne({ email: req.query.email }, (err, user) => {
if (err) {
res.send(err);
}
if (user == null) {
res.status(400).send("Non existing user");
}

// we have the user record from db, now check the password
const validPassword = bcrypt.compareSync(
req.query.password,
user.password
);
if (!validPassword) res.status(400).send("Not valid password");

// create and send a token to be able to use it in further requests
const token = jwt.sign({ _id: user._id }, process.env.TOKEN_SECRET);
res.header("auth-token", token)  // set the token in the header of the response

.send(token); // display the token
});
});
};


routes.js: // our main routes file
import { addNewUser, loginUser } from "../controllers/controller.js";
import { info } from "../controllers/info.js"; // the protected route

import { auth } from "../controllers/verifyToken"; // middleware for validating the token

const routes = app => { 
app.route("/user/register").get((req,res)=>addNewUser(req,res)); // we capture inside req, and res
app.route("/user/login").get((req,res)=>loginUser(req,res)); // we capture inside req, and res
app.route("/info").get(auth,(req,res)=>info(req,res)); // we capture inside req, and res
// and insert the auth middleware to process the token
};
export default routes;


verifytoken.js
import * as jwt from "jsonwebtoken";

export const auth = (req, res, next) => {
const token = req.header("Bearer");
if (!token) return res.status(401).send("access denied");
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
if (!verified) res.status(400).send("Invalid token");
// continue from the middleware to the next processing middleware :)
next();
};

// user mongoDB schema:
user.js
import mongoose from "mongoose";
export const userSchema = new mongoose.Schema(
{
name: { type: String, required: "Enter username", minlength: 5, maxlength: 20 },
email: { type: String, required: "Enter email", maxlength: 50, unique: true },
password: { type: String, required: "Enter password", maxlength: 65 }
},
{
timestamps: true
}
);

Congratulations and enjoy learning !

Monday, November 25, 2019

Create REST API with NodeJS

An introductory part of the Learn Node.js, Express and MongoDB + JWT course.


First, we will install the latest npm and nodejs version from nodesource: https://github.com/nodesource/distributions

curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
sudo apt-get install -y nodejs

Then we will create a directory API, where we will set up our project:
mkdir api
npm init
(please provide project name, description, and author name as information)
Then is time to install some required packages:
We will install express in order to act as a web server together with nodejs: npm i express
Open the package.json file and notice how now express appears under the dependencies section.

We will also install MongoDB as well as MongoDB as our database and a mongoose DB connection helper with npm i mongodb mongoose
Then we will do a babel installation. Babel (/preset-env) will transpile (convert) our code so it can become cross-browser compatible, this is especially useful when you want to achieve better JavaScript support. Babel (/node) will provide understanding to nodejs of the modern ES features we will be using inside the index.js file.
npm i --save-dev @babel/preset-env @babel/core @babel/node
Now if you open package.json you will see that babel is installed only as a development dependency. Before start using babel we will configure it, just create a file .babelrc with the following content:
{
"presets": ["@babel/preset-env"]
}
Here we are just preparing the Babel transpiling to be suited for specific browsers.

We will also install nodemon to be able automatically to monitor and refresh the nodejs server (loading the new code) when we make changes to our code.
npm i nodemon --save-dev
/* Later we will need  body-parser to be able to parse when receiving as well as send application/json type of requests: npm i body-parser
*/

Now let's change the starting script of package.json to be able to run the upcoming index.js file. We will change it from 'test' to:
start: 'nodemon --exec babel-node index.js" //note the double --
With this change, we now can just type: npm start and it will start recompiled / transpiled version of index.js (using babel) so we can then browse the generated version of index.js via our browser.

In order to be able to browse the output of the index.js file, we will build a server that will serve it. The server will be bound to the localhost IP address and will listen to a specific port.
Create index.js with the following content:
import express from 'express'; // we import the express library
// since we are using babel the import function will be recognized in browsers which are not supporting ES6 imports
const app = express(); // we initiate a class object from the express library
// note the usage of const -> we will not change the app variable later so const is well appropriate to be used here
const PORT = 4000; // let's define a listening port
we will also create our first request route point / :
app.get('/',(req,res) => res.send("server running"));
// basically we say when you have request for the root url / serve them message: "server running"
app.listen(PORT,()=>console.log('listening on ${PORT}));
// here we are just listening to port 4000 and displaying information to the console where the express server starts running.

Now we can just type: npm start and browse inside: http://localhost:4000 to see our application running!

Ok, let's use mongoose to connect to our MongoDB database:
import mongoose from 'mongoose';
then we will connect with the following options:
mongoose.connect(
'mongodb://localhost/my_db',
{
useNewUrlParser: true,
useUnifiedTopology: true
}
);

...
We will proceed with a standard MVC pattern, where we will be having routes that will get browser requests directing them to specific controllers, controllers that will take care of the logic and will use models to perform various actions on the MongoDB database. Index.js will load up everything from a new subdirectory /src/, where our routes, models, and controllers will reside.

Routes
Now it is time to create our routes properly. We will create directory /src/routes/ with file routes.js inside
there we will define and export the routes for our application like so:
const routes = (app)=>{
 app.route('/user')
.get((req,res)=>res.send('Getting information about all users'))
.post((req,res)=>res.send('Creating new user'));
 app.route('/user/:userID')
.get((req,res)=>res.send('Getting specific user by ID'))
.put((req,res)=>res.send('Updating user by ID'))
.delete((req,res)=>res.send('Deleting user by ID'));
}
export default routes;

inside index.js we will import the created routes;
import routes from '/src/routes/routes';
then we will load up those routes with:
routes(app);

Using MongoDB
We will create a schema for of how our documents inside MongoDB we would like to look like, inside the /models/model.js file:
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
export const UserSchema = new Schema(
{
firstName: {type:String,required: 'Enter firstname'},
lastName: {type:String,required: 'Enter lastname'},
email: {type:String,required: 'Enter email'},
created_at: {
type: Date, 
default: Date.now
}
});

Alright, we will use this UserSchema, when gradually creating our controllers.
But before all this, lets recognize requests we will be working with (inside index.js):
// we will be using the built-in middleware functions in Express, so we can parse requests having JSON payloads inside
to parse the request text of type: application/json
app.use(express.json());
and to parse the request text of type application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

Then we create a directory with file: controllers/controllers.js
import mongoose from 'mongoose';
import {UserSchema} from '../models/model.js'; //so we will be able to use our newly created UserSchema.
const User = mongoose.model('User',UserSchema); // here we create 2 things: 1) User constant which will hold a reference to the User model, 2) which we created from the provided UserSchema.

We will now create our first function that will work with MongoDB and instruct the model to create new User.
export const addNewUser=(req,res)=>{
let newUser = new User(req.body); // we use the whole request body to create a new User model based on the UserSchema.
// Once we have the model we can use its functions such as save, find, findOneAndUpdate and others, which are provided from MongoDB.
newUser.save((err,user)=>{
if (err){res.send(err);} // we send an error if we couldnt create the db document
 res.json(user); // if everything went well, we output the created document as a json format
})
}

now we go back and modify our routes.js to be able to use the newUser function:
1) import addNewUser: import {addNewUser} from 'controller';
2) change .post method to: post(addNewUser);

Next we copy the newUser function and will modify it to create function that will get all of our Users:

export const getUsers=(req,res)=>{
User.find({}(err,users)=>{ // note here we are finding all users via the {} empty search condition, also note that we don't need to create another instance of User, we just use the already created one above.
if (err){res.send(err);}
 res.json(users);
})
}
Now again in routes.js we import the getUsers function, and change the .get method to:
.get((req,res)=>getUsers);
And by the way you can use PostMan to test if the routes are working correctly !

Now for the user/:userID routes:
// we will use mongoDB .findByID() to get specific user by its ID
export const getUserByID=(req,res)=>{
User.findById(req.params.userID, (err,user)=>{ // note here we are getting the  userID from the request parameters
if (err){res.send(err);}
 res.json(users);
})
}
don't forget to update the get method under the '/user/:userID' route in order to activate the getUserByID function: .get(getUserByID)

Let's update some users:
export const updateUser = (req,res) => {
User.findOneAndUpdate(
{_id:req.params.userID}, // here we will be searching first by _id which equals to the passed inside of :userID
req.body, // we pass the request containing the updated information
{new:true, useFindAndModify:false}, // with new:true we will be returning the newly updated user to the res.json
(err,updateduser) =>{
if (err){res.send(err);}
res.json(updateduser); // we send as json the updated user information

});
}

Try to create the deleteUser function by yourself, it will be using the userID parameter just the way updateUser function did. Hint: you can use the .remove() MongoDB function!

Congratulations and enjoy learning !

Subscribe To My Channel for updates