Sai Yerni Akhil's Blog

Sai Yerni Akhil's Blog

Creating an app to help send your friends a self-destructing message - PART 1

Creating an app to help send your friends a self-destructing message - PART 1

So looking at the problem, rather than diving straight into the "how" part, let's look at the problem with the 4 W thinking lenses ( Why, Where, What and Who ) this helps us arrive at the optimum solution. In our case, the What part is - creating an app that allows us to send our friends things like youtube links or some secret stuff, that expires after a certain time, the Why part - we usually share with our friends the usual memes, trending Youtube/Instagram videos and the messages we share don't need to be stored permanently and we won't be revisiting that anytime, so there needs to be a way to store them temporarily. Thus we arrived at this solution. The Who part will be our friends, colleagues, or anyone who has access to the internet.

Now diving into the How part, starting with the backend part of the application, we are gonna design it starting with the actions users might perform.

  1. The user enters a message he wishes to send, sets an expiry date/time.
  2. The user then gets back his message, which the user can use to share.

So to build the backend of the application, I'm gonna use node.js with Express.js, and MongoDB.

Setting up an Express.js app

cd <project-name>
npm init
npm install express

Give the author's name and all the fields when prompted as you wish, during the setup.

The utilities are added to the Express app as middlewares. So have used quite a several middlewares while building the application.

Starting the server

/index.js

const express = require('express')

const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }));

const PORT = 8082

app.get("/", (req,res) => {
    return res.send("Hello, World")
})

app.listen(process.env.PORT || PORT, () => {
    console.log(`Backend is running on port ${process.env.PORT || PORT}`)
})

The line app.use(express.json()), is a middleware used to tidy up the request payloads we get from the user to be able to use them with JavaScript. The line app.use(express.urlencoded({})) is an option to choose how to parse the incoming request object (qs or querystring). More details at here.

I'm now done with the initial setup, now going into the actual development based on the points we have listed above. The first task being, get the data from the user then store it inside our database. So, for that, we need a POST method, which takes the data from the user through a form. Since we haven't had our front-end form ready, I'm going to use a tool called Postman which is a tool used to interact and test APIs. The endpoints in the Express are termed as called routes.

Setting up the MongoDB client

There is good support for MongoDB in node.js and so there are several libraries available. So I have used the official native driver provided by MongoDB.

/index.js

...
const MongoClient = require('mongodb').MongoClient
const DB_USERNAME = process.env.DB_USERNAME
const DB_PASSWORD = process.env.DB_PASSWORD
const DB_NAME = process.env.DB_NAME
const DATABASE_URL = `mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@cluster0.yqxu2.mongodb.net/${DB_NAME}?retryWrites=true&w=majority`
MongoClient.connect(DATABASE_URL,{useUnifiedTopology: true})
.then((client) => {
    const db = client.db(DB_NAME)
    const messagesCollection = db.collection('newCollection')
    console.log('Connected to the Database - SUCCESS!')
    ...
}).catch(err => {
    ...
)}

...

The client object we got in the then(...) part of the promise is what is needed for us to interact with the Mongo server. Hence all the logic and routes involving the database comes in it.

I have hosted my database on MongoDB atlas. To store all the database credentials, I have used a utility called dotenv, I have created a .env to store all my credentials as key-value pairs separated with an =. Storing credentials as a const in the js files is a bad practice, as others viewing your files will get to know your credentials.

The POST method

route - /add

/index.js

...
app.post("/add",(req,res) => {
        const uuid = nanoid(7)
        const insertDate = new Date() 
        const millisSecondsFromNow = req.body.time * 1000
        const expireAt = new Date(insertDate.getTime() + millisSecondsFromNow)
        const newMessageDocument = {...req.body, expireAt, insertDate, _id: uuid}
        messagesCollection.insertOne(newMessageDocument)
        .then(result => {
            return res.status(201).send({ uniqueUrl : uuid, expireAt})
        }).catch(error => {
            return res.status(503).send(error)
        })
    })
...

The task associated with this endpoint is to store this in the database, which is creating a resource, hence the use of the POST method. To create a unique field, to access the document, I have here used a library called nanoid which gives the UUID of a specified length (here 7). I am getting the time after which the message should be expired in seconds from the time of creation and then I increment the current time with the seconds from the user to arrive at the expiry date. Thereafter I insert them into my collection in the database and respond to the user with apt response codes. You can know more about HTTP response codes - here.

While we move into the next part of the application. I will quickly dive into the database design of our database and how we achieved the timely deletion of the records after a specified time?

The only data we need to store is the message, the insert date, the expiry date and the time active (in seconds). hence a sample document of the data would be like -

chrome_QMmPV2wUag.png

The time given is 10000 seconds which is equal to around 2.77 hours. hence, the field expireAt is 2.77 hours from the insertDate.

How do we test if that's working fine? Using postman!

Postman_uFbO79XIub_LI.jpg

We can select the type of request from the dropdown on the left of the address bar. Now its POST and the endpoint /add in the address bar. We can now see the request body with two fields from the user, message and time. And down below we get to see the response from the server, which has uniqueURL which I'll discuss going forward and the time at which the message expires.

How did we achieve, timely self-destruction?

Coming to the MongoDB server, it has a background thread mongod (mongo daemon) which always runs in the server. There's a feature in MongoDB called, Time-to-Live (TTL), the mongod thread always runs and checks to delete the documents at a trigger time. It can be the same time for all the records or the column specifying the delete time. How to add this check to our database collection?

db.<collection_name>.createIndex({expireAfterSeconds: 3200}) - adding this makes the records to auto-delete after 3200 seconds but we need different units of time (minutes, seconds, hours or even days) hence I create an index expireAt the time after which I intend to delete the document after. Using this - db.<collection_name>.createIndex( {"expireAt": 1}, {expireAfterSeconds: 0} ). You can know more about the feature at here.

Going to the next part of our solution, which is the user getting back his message from the database.

The GET method

route - /retrieve/:uuid

/index.js

...
MongoClient.connect(DATABASE_URL,{useUnifiedTopology: true})
.then((client) => {
...
app.post("/add",(req,res) => {
...
})

app.get("/retrieve/:uuid",(req,res) => {
        const uuid = req.params.uuid
        messagesCollection.findOne({_id: uuid})
        .then(result => {
            if (result === null) {
                return res.status(404).end()
            }
            else
                return res.status(302).send(result)
        }).catch(err => {
            console.log(err)
            return res.status(503).send(error)
        })
    })

...

In this part we are accessing a record from the database neither creating nor updating, hence we have used the GET method. How do we access the particular record? If we are in a Relational Database, we use a primary key, At present, we have a similar index in our collection called the _id, which is the UUID generated by nanoid. We get this UUID from the user and our server responds accordingly if its found then return the document else return null with apt response codes.

Testing the endpoint in postman

Postman_ULh0Vm5DIJ.png

In the address bar, we see the retrieve/ suffixed by the UUID we got previously. And we got the response from the server, all the details of the message like (_id, expireAt, insertDate, message and time)

Now all the tasks we intended after the analysis are done and we now got the working version of the backend with us.

You can find the source code at - github.com/saiyerniakhil. The final application is hosted at - https://short-url-gen.netlify.app/

The link to the second part of the article -

 
Share this