Create a light REST API web application using Bottle framework Vuejs and MongoDB Part2

MongoDB_Rest API_Bottle framework_Python_JWT_VueJS_API_PyMogo_Linux_

In part 2 of this tutorial we will cover first some basic MongoDB functions using both mongo prompt and using python library pymongo and finally we will update the register and login page to use the database .

As I mentioned in part 1 of this tutorial, we will be using MongoDB as our database.

MongoDB is an open-source document database and leading NoSQL database.  A document oriented database means that the data is stored and queried as a JSON like format .

Two terms that are often mentioned with NoSQL are  collections and documents .

  • A Collection is a group of MongoDB documents and  it is an equivalent to a table in a RDBMS database
  • A Document is a set of key-value pairs such as  { "title" : "This is a Title", "description" : "This is a Description" } and that's somewhat an equivalent to a row in a RDBMS database.

I will not go in-depth about MongoDB but rather only the functions that we will use in this tutorial, If you want to learn more check this link. It's a good place to start .

Installing mongoDB:

I'm using an Ubuntu 18.04 so an apt install should do the trick . Check the install instructions for your Linux distribution if you are using a different one.

apt install mongodb

Once the install is complete, verify that mongoDB service is running :

systemctl status mongodb
● mongodb.service - An object/document-oriented database
   Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2021-05-26 08:49:17 +01; 6h ago
     Docs: man:mongod(1)
Main PID: 1236 (mongod)
    Tasks: 23 (limit: 4915)
   CGroup: /system.slice/mongodb.service
       └─1236 /usr/bin/mongod --unixSocketPrefix=/run/mongodb --config /etc/mongodb.conf
If it's not active (running)  start it using :

systemctl start mongodb

If you want it to automatically starts on boot :

systemctl enable mongodb

Or disable automatic start on boot with :

systemctl disable mongodb

Preparing collections and documents

Let's cover now some basic MongoDB commands to prepare our Collections and documents .

Enter mongoDB prompt using :

mongo
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.3
>

First we select a database or create a new one if it doesn't exists . I named it notes_app

> use notes_app
switched to db notes_app

You can check the database currently in use with :

> db
notes_app

To create a new Collection use the command :

> db.createCollection("notes")
{ "ok" : 1 }
> db.createCollection("users")
{ "ok" : 1 }
Go ahead and create the notes and users collections , we will be using those with our application .

The JSON structure for the users is :
{
    "username" : "ouslab",
    "password" : "Some_password",  "token" : "some_random_string"
}

So we have a username and password field , and also a token field that we will use once we add the Authorization later using JWT.

The JSON structure for the notes will be :

{
    "title" : "note title",
    "description" : "note description",
    "user_id" : ObjectId("609be9e9d54ed6102b351eb1")
}
Pretty basic as well, we have two fields , and a user_id field that we will use to specify which user the note belongs to .

Let's insert a user :

db.users.insert({ "username" : "ouslab", "password" : "Some_password ",  "token" : "some_random_string" })
WriteResult({ "nInserted" : 1 })

To list the documents of a collection use the find function :

> db.users.find()
{ "_id" : ObjectId("60afbfb9c38dce109f9bccc7"), "username" : "ouslab", "password" : "Some_password ", "token" : "some_random_string" }

If we don't specify the _id parameter, then MongoDB assigns a unique ObjectId for this document.

We can filter the results of the find function by giving it the keys and desired value :

> db.users.find({"username":"ouslab"})
{
    "_id" : ObjectId("60afbfb9c38dce109f9bccc7"),
    "username" : "ouslab",
    "password" : "Some_password ",
    "token" : "some_random_string"
}
> db.users.find({"_id": ObjectId("60afbfb9c38dce109f9bccc7")})
{ "_id" : ObjectId("60afbfb9c38dce109f9bccc7"), "username" : "ouslab", "password" : "Some_password ", "token" : "some_random_string" }

The find function returns an array , if you want only one document, we will use the findOne() function :

> db.users.findOne({"username":"ouslab"})
{
"_id" : ObjectId("60afbfb9c38dce109f9bccc7"),
"username" : "ouslab",
"password" : "Some_password ",
"token" : "some_random_string"
}

To update a document we use the updateOne() function as follow :

> db.users.updateOne({"username":"ouslab"},{"$set": {"username":"some_username"}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.users.find()
{ "_id" : ObjectId("60afbfb9c38dce109f9bccc7"), "username" : "some_username", "password" : "Some_password ", "token" : "some_random_string" }

The first argument {"username":"ouslab"}  selects the document we want to update, in this case the document that have username equals to "ouslab" and with the second argument we are requesting to update the username to "some_username"

And finally to delete a document we use the remove function :

> db.users.remove({"username" : "some_username"},1)
WriteResult({ "nRemoved" : 1 })

This will delete all documents that has "some_username" as username, and with the second argument set to 1 we are requesting to delete only the first one .

If this looks a bit confusing, don't worry it will get easier when using the pymongo library which we will cover next .

Installing pymongo

Use pip to install pymongo as well as bson which will provide functions to manipulate ObjectId of documents.

Make sure you are still in the active python virtual environment we have created in part 1 .

pip install pymongo bson

Let's thinker a bit with the library in a python interpreter prompt and review some of the CRUD functions we've tried above, but this time with python .

Open the python interpreter prompt and import pymongo and ObjectId from bson library :

python
Python 3.6.9 (default, Oct 8 2020, 12:12:24)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pymongo import MongoClient
>>> from bson.objectid import ObjectId

Then create an instance of MongoClient :

>>> client = MongoClient()

By default it is set to use the local mongoDB database, if you want to specify a specific ip and port , you can use :

>>> client = MongoClient('localhost',27017)

Then we select the database , in our case it will be notes_app :

>>> db=client.notes_app

We can insert a document with :

>>> db.users.insert({"username":"ouslab","password": "some_password","token":""})
ObjectId('60b0b66ec2a7d396353ac2f8')

We can list the documents with :

>>> users=db.users.find()
>>> list(users)
[{'_id': ObjectId('60b0b66ec2a7d396353ac2f8'), 'username': 'ouslab', 'password': 'some_password', 'token': ''}]
>>>

Notice that i have used the list function to convert from a mongoDB collection to python list

We can filter the  the results same way  we did in mongo prompt, this time we will use ObjectId to get a specific document :

>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')})
>>> user
{'_id': ObjectId('60b0b66ec2a7d396353ac2f8'), 'username': 'ouslab', 'password': 'some_password', 'token': ''}

To update documents we use update or update_one  function :

>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')}) 
>>> db.users.update_one(user, {"$set" : { "username" : "new_username" } } )
<pymongo.results.UpdateResult object at 0x7f52e914aec8>
>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')})
>>> user
{'_id': ObjectId('60b0b66ec2a7d396353ac2f8'), 'username': 'new_username', 'password': 'some_password', 'token': ''}

Notice here that i have fetched the document first in the user variable, then passed it to the update_one function as a first argument

And finally we can delete a document with delete or delete_one function :

>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')})
>>> db.users.delete_one(user)
<pymongo.results.DeleteResult object at 0x7f52e917b108>


Update the Login and Register

Okay now that we have seen most of the necessary database operations we will need in our application, lets go back to Bottle .

In server.py  we import pymongo and bson , and setup the mongoClient towards the beginning of the file :

from bottle import route, run, template ,static_file, redirect , request
import os
import json
import hashlib
from pymongo import MongoClient
from bson.objectid import ObjectId

client = MongoClient()
db=client.notes_app

...

Import hashlib as well, we will use it next in the user registration to make passwords hash before storing them in the database .

I will create two functions one that generates a hash of a password and the second verifies if the  hashs of password and a hash string are equal. The functions will  use the SHA-256  hashing algorithm  using pbkdf2_hmac function of the hashlib library to learn more check out this blog

def password_hash(password):
salt= os.urandom(32)
key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'),salt,100000)
return salt+key

def verif_password_hash(password,key):
hash_key = hashlib.pbkdf2_hmac('sha256',password.encode('utf-8'),key[:32],100000)
return key[32:] == hash_key

And now we can update our register_post function :

@route('/register', method='POST')
def register_post():
username = request.json['username']
password = request.json['password']

user=db.users.find_one({"username":username})
if user :
return_data = { "error": "1", "message": "User exists"}
else :

password = password_hash(password)
db.users.insert_one({"username":username,"password":password,"token":""})
return_data = { "error": "0", "message": "Success"}


return json.dumps(return_data)

First we search in the database to see whether the username already exists using the find_one function, if not we create a hash of the password  and we store the username, the hashed password and an empty token in the database . And finally we return a Json string containing the result of the operation .

Save and rerun the server.py python script , and try register a new user  :


If you check in mongo prompt, you will find the user was created :

> db.users.find()
{ "_id" : ObjectId("60b0cb006cfc74e7c99128b8"), "username" : "ouslab", "password" : BinData(0,"bfy6QRF4gnwIlLi/6QJhzXqoQds7L7ymf6EeYfyIDcnzTzTi9r/05rNC95S5IlXGePRkICt0fd+zvw1HH+z+bA=="), "token" : "" }

Now for the authentication , let's create a new function with the following content :

@route('/authentication', method='POST')
def authentication():
username = request.json['username']
password = request.json['password']

user=db.users.find_one({"username":username})
if user :
if verif_password_hash(password,user['password']) :
return_data = {"error": "0", "message": "Success"}
else :
return_data = {"error": "1", "message": "Fail"}
else :
return_data = {"error": "2", "message": "User doesn't exist"}

return json.dumps(return_data)

We simply search for the user by username , if it exists we compare the hash of the provided password with the hash stored in the database and finally we return a Json string with a specific error code and message depending on each case .

Don't forget to update the login.html page as well , it should look something like this :

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Login</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="login-form" id="app">
<form>
<h2 class="text-center">Log in</h2>
<div class="form-group">
<input type="text" class="form-control" placeholder="Username" required="required" name="username" id="username" v-model="username">
</div>
<div class="form-group">
<input type="password" class="form-control" placeholder="Password" required="required" name="password" id="password" v-model="password">
</div>
<div class="form-group">
<button type="button" class="btn btn-primary btn-block" v-on:click="login">Log in</button>
</div>
</form>
<p class="text-center"><a href="/register">Register</a></p>
</div>
<script src="/static/js/jquery-2.2.4.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/vue2.js"></script>
<script src="/static/js/axios.min.js"></script>
<script>
var vue = new Vue({
el : "#app",
data: {
username: "",
password: ""
},
methods: {
login: function(){
var _this = this
const data = JSON.stringify({'username':_this.username,'password':_this.password});
axios.post('/authentication', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
console.log(response.data);
if(response.data['error'] != 0){
console.log("No action");
}else{
window.location.href='/';
}
})
.catch(function (error) { console.log(error);});
},
}
});
</script>

</body>
</html>

rerun the server.py script , and try to login with the user that you registered previously, if it's successful you should be redirected to "/" and see the hello work page .

In the next part of the tutorial, we will implement the CRUD functionalities for the application.

Comments :