Meet NoSQL.js, an in-memory database through REST API.
Tags: nosqljs, node, nosql, inmemory
Published: 2021-06-07 23:21:05

<p>If you need an in-memory database through REST API that can load and aggregate external data into a single api with support for persistence to a json file, a middleware hook ready to log api requests while also updating your relational database, you might find this post interesting.</p>

<p>We all know database operations are costly and is always a good idea to have some sort of cache of it near our frontend application for faster access and operations and is also much better to have to interact with it through a rest api without the need to interact with all that drivers and connections stuffs.</p>

<b>What problem does i think it solve?</b>

<p>This post is going to be a long one. I am going to show and talk about my open source project of an in-memory rest database made in NodeJS with no addition of external libraries so ever, just raw NodeJS to handle all API´s requests, that can be used for cache as a service, mock, database aggregator for a rest service and can even update your database with out-of-the-box support for a middleware hook so you can log operations in the api and handle database updates if you wish.</p> 

<p>The hot feature for me is the out-of-the-box support for loading and converting a relational database into a rest service that you can access and update. In this version it came with support for adding mysql tables into a web api. You can even aggregate it with other sources of data like json files if you want and it was all thought with a repository pattern in mind so we can plug others vendors as well making it possible to aggregate different database sources. The next one will be PostgreSQL.</p> 

<p><b>Project structure?</b></p>
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-P_SfWeKjm18/YLUEZQ2Mv3I/AAAAAAAAGE0/bcyiihljAXgGpHvm8o9xjTq8PZ_xY_s3wCLcBGAsYHQ/s0/nosql.PNG" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="773" data-original-width="359" src="https://1.bp.blogspot.com/-P_SfWeKjm18/YLUEZQ2Mv3I/AAAAAAAAGE0/bcyiihljAXgGpHvm8o9xjTq8PZ_xY_s3wCLcBGAsYHQ/s0/nosql.PNG"/></a></div>

<p><b>How does it work?</b></p>

Since i will be running the mysql database in a docker, let´s start up our container and access its bash with the docker exec command so we can interact with the database client:
<pre style="background-color:#eee !important;padding-left:10px;">
docker run --rm --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d -p 3306:3306 mysql:5.7

docker exec -it mysql bash
</pre>

<p>The reason i am doing this is because i want to load and make the database data available through a rest api. We will see how this is done later in this post.</p>

<p>We will see two different ways to create the database and mysql table. One way using the MySQL CLI and one using a schema file. To get access to the MySQL CLI enter the following command in the bash and type the same password provided in the docker run command:</p>

<pre style="background-color:#eee !important;padding-left:10px;">
mysql -u root -p
</pre>

<p>Next enter the following command to create our products database:</p>

<pre style="background-color:#eee !important;padding-left:10px;">
CREATE DATABASE products;
</pre>

<p>We can now switch to our newly cretaed database so we can create our table in it.</p>

<pre style="background-color:#eee !important;padding-left:10px;">
USE products;

CREATE TABLE Product (
    id int NOT NULL AUTO_INCREMENT,
    price float,
    date datetime,
    PRIMARY KEY (id) 
);
</pre>

<p>The other way involves the creation of a Dockerfile with the following information:</p>
<pre style="background-color:#eee !important;padding-left:10px;">
FROM mysql:5.7

COPY ./product.sql /docker-entrypoint-initdb.d

ENV MYSQL_DATABASE=Products
ENV MYSQL_ROOT_PASSWORD=123456

EXPOSE 3307
</pre>

<p>Here we are copying our product schema file to the /docker-entrypoint-initdb.d. Every sql file we place there will be executed after the database is created with the provided MYSQL_DATABASE environment variable.</p>

<p>Run the following command to create an image based on our Dockerfile.</p>
<pre style="background-color:#eee !important;padding-left:10px;">
docker build -t cjafet/mysql .
</pre>

<p>Next, run our new image in the container:</p>
<pre style="background-color:#eee !important;padding-left:10px;">
docker run --rm --name mysql -p 3306:3306 cjafet/mysql
</pre>

<p>Now that we have our database ready, let´s run our tests to see if we are all good to go:</p>

<pre style="background-color:#eee !important;padding-left:10px;">
./node_modules/mocha/bin/mocha
</pre>

<p>Since all tests are passing let´s start our in-memory rest database server:</p>

<pre style="background-color:#eee !important;padding-left:10px;">
node ./server/nosql-server.js
</pre>

<p><b>Available endpoints</b></p>
<p>To get data from a table in the api you just need to specify its name in the following format:</p>
<pre style="background-color:#eee !important;padding-left:10px;">
http://host:port/getkey/table-name
</pre>

<p>Let´s try out making some GET and POST requests to see what we get from our newly created api.</p>
<p>This is how it works. Suppose we want to make a GET request to get all data imported from our <b>product</b> table into our rest api. In that case we would need to make a GET request to the following endpoint:</p>
<pre style="background-color:#eee !important;padding-left:10px;">
http://localhost:7700/getkey/product
</pre>

<p>To get all available data from our in-memory database we would simple have to make a GET request to the /get endpoint which in turn would return data with all of the availables tables from our in-memory database each one representing a different json property.</p>

<pre style="background-color:#eee !important;padding-left:10px;">
http://localhost:7700/get
</pre>

<p><b>You can also test all GET requests direclty from your browser.</b></p>

<p>Now let´s make a POST request to add a sales table to our in-memory database representing the sales of the products available into our api. This is how we should make a POST request to add it to our database:</p>

<pre style="background-color:#eee !important;padding-left:10px;">
POST /setkey/ HTTP/1.1
Host: localhost:7700
Content-Type: application/json

{
    "db": "sales",
    "data": [
        {
            "id": 1,
            "product-id": 100,
            "client-id": 1,
            "value": 100.89
        },
        {
            "id": 2,
            "product-id": 101,
            "client-id": 2,
            "value": 88.99
        },
        {
            "id": 3,
            "product-id": 102,
            "client-id": 3,
            "value": 29.99
        }
    ]
}
</pre>

<p>As you can see we just need to make a POST request with two json properties: <b>db</b> and <b>data</b>. One representing the table name and the other one representing your table data.</p>

<p>After the request your db.json file should look like this:</p>
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-9iT154Hs8KE/YLVvFjPAd6I/AAAAAAAAGFE/hEzgYbmW4Vo45O8asa5d2BdJYbNaBA13gCLcBGAsYHQ/s0/sales-db.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="176" data-original-width="1268" src="https://1.bp.blogspot.com/-9iT154Hs8KE/YLVvFjPAd6I/AAAAAAAAGFE/hEzgYbmW4Vo45O8asa5d2BdJYbNaBA13gCLcBGAsYHQ/s0/sales-db.png"/></a></div>

<p>You should also be able to see the requested information logged in the logs.txt file:</p>
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-h4t7izWxYD8/YLVvw_hS0VI/AAAAAAAAGFM/pavi3OWTl8cNFCh9nwgd_DLiqlQjZWvPgCLcBGAsYHQ/s0/log.PNG" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="266" data-original-width="1309" src="https://1.bp.blogspot.com/-h4t7izWxYD8/YLVvw_hS0VI/AAAAAAAAGFM/pavi3OWTl8cNFCh9nwgd_DLiqlQjZWvPgCLcBGAsYHQ/s0/log.PNG"/></a></div>

<p>I will now make another POST request to add products to the nosqljs in-memory database.</p>
<pre style="background-color:#eee !important;padding-left:10px;">
{
    "db": "Product",
    "data": [
        {
            "price": 199.99,
            "date": "2021-01-01 10:10:10"
        }

    ]
}
</pre>

<p>Since we are making a request that also matches our Product table in the mysql database, our middleware will intercept the request and add it to the database as well.</p> 
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-7bHFP6kxlbs/YLV0jsP8AiI/AAAAAAAAGFk/0y2bE-wOjn00g_XlsLdq4Etjn1T7OwcpACLcBGAsYHQ/s0/config.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="349" data-original-width="1312" src="https://1.bp.blogspot.com/-7bHFP6kxlbs/YLV0jsP8AiI/AAAAAAAAGFk/0y2bE-wOjn00g_XlsLdq4Etjn1T7OwcpACLcBGAsYHQ/s0/config.png"/></a></div>

<p>Our db.json file should have another key added to it matching the db value of our POST request.</p>
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-RvooTAAQ3PQ/YLVymqaJ7SI/AAAAAAAAGFU/Qbk29ay6vMoZf-8iiTNpRnau4mApKkh-wCLcBGAsYHQ/s0/product-db.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="205" data-original-width="1289" src="https://1.bp.blogspot.com/-RvooTAAQ3PQ/YLVymqaJ7SI/AAAAAAAAGFU/Qbk29ay6vMoZf-8iiTNpRnau4mApKkh-wCLcBGAsYHQ/s0/product-db.png"/></a></div>

<p>Our log.txt file should have now one more line that matches our POST request.</p>
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-qP_611GLk7Q/YLVz_O4bS-I/AAAAAAAAGFc/ii9vY0SEm7YDEz0LRUofpgkwlOdZbeIEwCLcBGAsYHQ/s0/log-product.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="324" data-original-width="1315" src="https://1.bp.blogspot.com/-qP_611GLk7Q/YLVz_O4bS-I/AAAAAAAAGFc/ii9vY0SEm7YDEz0LRUofpgkwlOdZbeIEwCLcBGAsYHQ/s0/log-product.png"/></a></div>

<p>If we check our database we should have exactly one registry their with the same values of our request.</p>
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-kGJ9GhuDGjM/YLV1n9QH9LI/AAAAAAAAGFs/yKu9-t34s802CkUXozB-P3QZhCZLyflxQCLcBGAsYHQ/s0/mysql.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="546" data-original-width="1754" src="https://1.bp.blogspot.com/-kGJ9GhuDGjM/YLV1n9QH9LI/AAAAAAAAGFs/yKu9-t34s802CkUXozB-P3QZhCZLyflxQCLcBGAsYHQ/s0/mysql.png"/></a></div>

<p>If you are starting NoSQL.js server with a database that have some data in it, like we now have, all you have to do is configure your connection in the conn.js file in the repository folder and enable the mysql property of the config file by setting its value to true. When you start the server you should have all of your db records loaded into it. Any POST request should now update both your in-memory database and mysql. This is how our db.json file should look like if we are just starting our NoSQL.js server.</p>
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-O5Gt001-6AM/YLV5vJ-eSYI/AAAAAAAAGF0/OC5UHElAtlUlU85rOe4BOc8kBged1hJeQCLcBGAsYHQ/s0/load-from-db.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="146" data-original-width="1314" src="https://1.bp.blogspot.com/-O5Gt001-6AM/YLV5vJ-eSYI/AAAAAAAAGF0/OC5UHElAtlUlU85rOe4BOc8kBged1hJeQCLcBGAsYHQ/s0/load-from-db.png"/></a></div>

<p>To be able to use it in your application all you have to do is make a GET request to the /sales or product endpoints:

<pre style="background-color:#eee !important;padding-left:10px;">
http://localhost:7700/getkey/sales
</pre> 

<p>Suppose you now want to change a product price. You can make a PUT request to the /product endpoint like that:</p>
<pre style="background-color:#eee !important;padding-left:10px;">
PUT /updatekey/product HTTP/1.1
Host: localhost:7700
Content-Type: application/json

{
    "id": 1,
    "product-id": 100,
    "client-id": 1,
    "value": 18.89
}
</pre> 

<p>One thing to note here. The system will make a match of your product id and will update all its content based on this payload. So if you provide a different json structure with additional properties it will be accepted here.</p> 

<p>Let´s suppose our system crash and the database get restarted from docker or kubernetes and that the db.json is saved outside the container. What do you think you will get back from the api? Let´s find out by making another GET request to it!</p>

<pre style="background-color:#eee !important;padding-left:10px;">
http://localhost:7700/get
</pre>

<p>You should get a response with all of the data as you had before. This is because all changes are saved in a local json file so you can restore it later exactly as it was before if you need it.</p>

<p>So, how can we make a DELETE request? We have two choices here. Delete a single record from the table or delete the table itself from the database.</p>

<p>To delete a single record we just need to make a DELETE request to the desired endpoint passing the id of the record you want to delete as a path parameter, like this:</p>

<pre style="background-color:#eee !important;padding-left:10px;">
DELETE /deletekey/product/1 HTTP/1.1
Host: localhost:7700
</pre>

<p>If however you want to delete the entire database table from the <b>memory</b>, you make a DELETE request to its endpoint:</p>

<pre style="background-color:#eee !important;padding-left:10px;">
DELETE /deletekey/product HTTP/1.1
Host: localhost:7700
</pre>

<p><b>Please do not delete your table from your relational database. At least not before making sure you have a backup of it!</b><p>

<b>All commands used in this post</b>

<pre style="background-color:#eee !important;padding-left:10px;">
docker run --rm --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d -p 3307:3306 mysql:5.7

docker exec -it mysql bash

mysql -u root -p

CREATE DATABASE products;

USE products;

CREATE TABLE Product (
    id int NOT NULL AUTO_INCREMENT,
    price float,
    date datetime,
    PRIMARY KEY (id) 
);

docker build -t cjafet/mysql .

docker run --rm --name mysql -p 3306:3306 cjafet/mysql

./node_modules/mocha/bin/mocha

node ./server/nosql-server.js
</pre>

<p><b>Next steps</b></p>
Improve project documentation, increase the number of tests, integrate PostgreSQL, analyze the need of individual json files. Some tests have already been made with it. 

<p><b>GitHub Repository</b></p>
<a href="https://github.com/cjafet/nosqljs" target="_blank">https://github.com/cjafet/nosqljs</a>