DEV Community

Cover image for How I Made A NoSQL Clone With JavaScript Classes
Elijah Trillionz
Elijah Trillionz

Posted on

How I Made A NoSQL Clone With JavaScript Classes

This post was originally made in my blog - How To Make a NoSQL Clone With JavaScript Classes

Hello devers. In this article I am going to show you how I made NoSQL Database clone with JavaScript Classes. This project is a very short one, took me like 3 hours or so to complete it. Though it is short, it conveys in it the ability to begin the NEXT BIG THING IN NOSQL (I actually don’t have any plan for that). This program is precisely cloning MongoDB and Firestore database.

What Inspired me to make this Project.

Learning and working with classes inspired me to work on this project. I recently just completed the Promises module in codecademy’s JavaScript course. I thought it would be nice to practice what I just learnt, so I decided to build this project.

It may have been short, but I did face a bit of challenge while making firestore form of updating documents. I will show you how I resolved it on my own.

How this Database Works

Basically as a back-end web developer of full-stack web developer, you can create a document (works like creating modules in mongoose) and add as much data as you want with any data type of course. These documents are classes, so you will only be making a class that extends the document class.

With that class you made, you can make instances and play around with the functions (queries) of the database. It’s easier than you think. What functions or queries does this database have.

  1. create()
  2. update()
  3. findById()
  4. delete()
  5. allData()

Pretty basic right! Enough with the theory, let’s jump into the code.

Before we jump into code, I must emphasize that this code is towards a clean code. Everything I did was with the clean code principles in mind and I may make mention of some of them.

Making The Document as a Class

First thing I did was to create a file, named it database. Then i created a class, called it Document

class Document {
 constructor() {
  this.documentData; // array of data
 }
}
Enter fullscreen mode Exit fullscreen mode

Let’s leave it as that for now. So far we can easily tell that documentData is not assigned to anything. Now this is because I want the documentData to come from the class that will inherit this class.

Since this class is never going to be used directly, then we will not have any parameters. So it is compulsory that the class that will inherit this class should assign a value (array specifically) to the documentData

Adding The Queries/Functions to the Document class

Get All Data

In this query, I will use JavaScript Getters to handle this. I just love using it for getting anything in a class or object. Just a reminder, I have a full project (appointment booking app) that uses only JavaScript Getters and Setters in JavaScript Objects. Go check it out.

// still inside the Document class, right beneath the constructor function
get allData() {
 return new Promise((resolve) => {
   setTimeout(() => {
     resolve(this.documentData);
   }, 100);
 });
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

I declared an getter, named it allData (meaning it get’s all data. Good naming convention for clean code. To be more specific you could say allDataInDocument).

I also declared a promise that will resolve with all the document data after 100 milliseconds delay. And our allData accessor will return this promise whenever it is called.

Create a Data

This function/method will create a new data in the document.

// still inside the Document class, right beneath the allData() getter 
create(data) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   if (typeof data === 'object' && !Array.isArray(data)) {
    // make sure data is an object; new Object()

    this.documentData.push(data);
    return resolve(this.documentData);
   }

   return reject('Data must be an object');
  }, 500);
 });
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

I created a method, called it create, and passed a parameter to it data. This method will a return promise that will be rejected if data is not an object (arrays will be rejected too).

If the data is an object, then the promise will push that object to the documentData array and then resolve to all available data in that document so far.

One more thing I’d like to add to this method is the ability to generate it’s own id if there isn’t any provided by the developer. Just like mongoDB’s _id. In generating this id you could use uuid or some other dependencies but I want to keep it simple and free from dependencies. Here is how:

First I will put together different characters in a keyboard like:

const letter = 'abcdefghijklmnopqrstuvwxyz';
const allCharacters = `${letter}1234567890123456789123456789$&@*£€¥%${letter.toUpperCase()}`;
// then i will split the allCharacters into an array like so 
const allCharactersInArray = allCharacters.split('');
Enter fullscreen mode Exit fullscreen mode

The next thing I would do is make a function that will randomly pick characters from our array above.

function randomise() {
 const randomCharacter = allCharactersInArray[Math.floor(Math.random() * allCharactersInArray.length)];

 return randomCharacter;
}
Enter fullscreen mode Exit fullscreen mode

OK it’s getting more interesting. Now let’s make another function (the main function) to randomly select as many characters as we want and turn it into one string i.e:

function generateId() {
 const generatedIds = [];
 for (let i = 0; i < 16; i++) {
   suggest.push(randomise());
 }
 return generatedIds.join(''); // will generate a very strong id
}
Enter fullscreen mode Exit fullscreen mode

The for loop in the generateId function will randomly select 16 characters from our allCharactersInArray. DO NOT USE THIS AS A PASSWOD GENERATOR (it's not secured). Here is the full code snippet. Now let's use it in our Document class.

Note: All we did to generate id was done outside of the Document class. If we put the id generator feature in this class, we would have:

create(data) {
 return new Promise((resolve, reject) => {
   setTimeout(() => {
    if (typeof data === 'object' && !Array.isArray(data)) {
      // make sure data is an object; new Object()
      if (!data.id) {
       const generatedId = generateId();
       data.id = generatedId;
      }

      this.documentData.push(data);
      return resolve(this.documentData);
     }

     return reject('Data must be an object');
   }, 500);
 });
}
Enter fullscreen mode Exit fullscreen mode

We want to make sure that it only generates when an id has not been assigned to the data yet.

Finding a Data

To find a data, I will make a method that will find data by ids.

findById(id) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
    const doc = this.documentData.filter((datum) => {
    return datum.id === id;
   });
   if (!doc[0]) {
    return reject('This data does not exist');
   }

   return resolve(doc[0]);
  });
 }, 1000); // shouldn't take so much time
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

This function returns a promise that is resolved to a data if the data exists. We first of all looked up the given id to see which document has that id as an id. If we have any results we resolve the promise with that data else will reject with a message. The promise will delay for 1 second.

Updating a Data

In firestore, when updating a data, the document id of that data is used to get the data. This data is an object and is updated as an object. So that means we have a new object and an old object.

Old object as in coming from the database and new object from the back-end developer. In the new object, the keys/properties are compared with the old object and any properties that matches will have its value being replaced by the new object’s value. In code:

const oldObject = { name: 'John Doe', nationality: 'South Africa' };
const newObject = { name: 'John Doe Seth' };

oldObject.update(newObject) // update is a firestore query/method for updating documents.
// oldObject will return { name: 'John Doe Seth', nationality: 'South Africa' }
Enter fullscreen mode Exit fullscreen mode

Now if there is a new property from the newObject, firestore will simply append it to the oldObject. Now ours is gonna be exactly like this, but the catch is how do I replace an existing object with a new object. This is where I got stuck for some time. But I figured it out with loops. Here is the snippet.

// still inside the document class just beneath findById method
update(docId, newData) {
 // the docId represents the id either given by the back-end programmer or a default generated id for that document like mongodb's generated _id

 return new Promise((resolve, reject) => {
  setTimeout(async () => {
   try {
   // step 1, find the data in d database
    const oldData = await this.findById(docId);
    // step 2, update with new data

    // using firebase standards, updating a doc is with an object
    if (typeof newData === 'object' && !Array.isArray(newData)) {
     return resolve(changeDataInObjects(newData, oldData)); // changeDataInObjects is declared outside the document class in the next snippet
    }

    return reject('New data must be an object');
   } catch (err) {
    return reject(err);
   }
  }, 1200);
 });
}
Enter fullscreen mode Exit fullscreen mode
// outside the Document class
function changeDataInObjects(newData, oldData) {
 for (let i in oldData) {
  for (let j in newData) {
   if (i === j) { // i and j represents the property names of oldData and newData respectively
    oldData[i] = newData[j];
   } else {
    oldData[j] = newData[j];
   }
  }
 }
 return oldData;
}
Enter fullscreen mode Exit fullscreen mode

The changeDataInObjects function is what does the updating. The reason i brought it out of the Document class is because I wanted a clean code. I will talk more on that in the future. So you should subscribe to my newsletter.

The last method is delete.

Deleting a data

// inside the document class just beneath the update method
delete(id) {
  return new Promise((resolve, reject) => {
   setTimeout(async () => {
    try {
     const documentToDelete = await this.findById(id);
     const indexOfDocument = this.documentData.indexOf(documentToDelete);

     this.documentData.splice(indexOfDocument, 1);
     resolve(this.documentData);
    } catch (err) {
      reject(err);
    }
   }, 1000);
  });
 }
}
Enter fullscreen mode Exit fullscreen mode

Pretty straightforward.

Now that we are done with the Document class we will now export it with module.exports since it’s a node environment.

In a separate file, after I import the Document class I will create a class call it Users:

class Users extends Document {
 constructor() {
  super();

  this.documentData = [];
 }
}
Enter fullscreen mode Exit fullscreen mode

After I have done that, I will export the Users class.

In another file, I will name this file as server.js. This file is where I can make use of all the methods we have created via the Users class.

Let’s say I want a document for pro-users, I would say;

const proUsers = new Users();
Enter fullscreen mode Exit fullscreen mode

Now with that I can access proUsers.create(), proUsers.delete(), proUsers.update(), proUser.allData etc.

async function addNewUser() {
 try {
  await proUsers.create({
   id: 1,
   name: 'John Doe',
   username: 'johndoe@gmail.com',
  });
 } catch (err) {
   console.log(err);
 }
}
Enter fullscreen mode Exit fullscreen mode

Try to play around with the other methods.

Conclusion

This may have been a very small project, I did learn a lot from it. Also I believe it has added to my confidence as a programmer to go for more projects. That’s why you have to keep coding, it keeps you fit. Here is the complete code for this project.

I have a Discord Server that involves a lot of coding activities like coding tasks, project ideas, learning new technology, project challenge, 100DaysOfCode etc. I have made two bots currently on my own just to make this server helpful to all programmers and I am only at the starting point.

Also I now have my blog, which explains why I have not always been posting here. So get acquainted with my blog, join the newsletter so that you can follow along with all posts I make exactly when I make them. Am not going to post on dev.to Everytime again. It's called Web Dever Guide, for web Developers.

I recently just made a post on How to become a Web Developer in 2021.

Thank you for reading. Have a wonderful time coding and learning.

Top comments (2)

Collapse
 
eissorcercode99 profile image
The EisSorcer

Thank you again for your Hard work Mr. Trillionz!

Collapse
 
elijahtrillionz profile image
Elijah Trillionz

I really appreciate you for that, really encouraging