Node js Race Conditions

Riddhesh Ganatra
4 min readJan 4, 2023

--

One of the basic concepts for node js developers is understanding race conditions. Let's make a simple API that registers users for a newsletter to understand race conditions. The only requirement is user should be able to register only once.

We will use Node js, Express js, and MongoDB for development.

Step 1: Install Mongo DB

We will use docker to start the local MongoDB instance.

docker run -p 27017:27017 --name newsletter -d mongo:latest

Note: Please map volume if you want to persist data.

Step 2: version 1 API that will return ‘success’ if it's a new email or error for already enrolled


function wait(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, timeout);
})
}

app.post('/v1/register', async (req, res) => {
try {
// find if user is already registered
let user = await client.db("newsletter").collection("usersV1").findOne({email:req.body.email})

if (user) {
return res.json({ message: `user already registered` })
}

// adding this line makes easy to test parallel requests from same user
await wait(10000)

const result = await client.db("newsletter").collection("usersV1").insertOne({email:req.body.email});

res.json({ message: `user registered successfully` })
} catch (error) {
console.log(error)
res.json({ message: error.message })
}
})

step 3: test version 1 API with curl

curl --location --request POST 'localhost:3000/v1/register' \
--header 'Content-Type: application/json' \
--data-raw '{
"email":"test1@gmail.com"

}'

When we hit this request from multiple terminals at the same time, same user is registered twice.

step 4: version 2API that will return ‘success’ if it's a new email or ‘error: for already enrolled

app.post('/v2/register', async (req, res) => {
try {
const result = await client.db("newsletter").collection("usersV2").insertOne(req.body);
res.json({ message: `user registered successfully` })
} catch (error) {
console.error(error.message)
res.json({ message: error.message })
}
})

step 5: create a unique index for email field when we connect to DB

client.connect().then(async () => {
console.log(`connected to DB`);
await client.db("newsletter").collection("usersV2").createIndex({ email: 1 }, { unique: true })
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
})

step 6: test v2 API

In the case of V2, never duplicate users will be registered, even when we get more than 1 request from the same user at the same time. We can also validate this using artillery or JMeter.

Complete code:

const express = require('express')
var bodyParser = require('body-parser')
const { MongoClient } = require('mongodb');

// const uri = "mongodb+srv://<username>:<password>@<your-cluster-url>/test?retryWrites=true&w=majority";
const uri = "mongodb://localhost:27017/newsletter";

const client = new MongoClient(uri);

const app = express()
const port = 3000

app.use(bodyParser.json())

app.get('/', (req, res) => {
res.send('Hello World to Race Conditions')
})


app.post('/v1/register', async (req, res) => {
try {
// find if user is already registered
let user = await client.db("newsletter").collection("usersV1").findOne({email:req.body.email})

if (user) {
return res.json({ message: `user already registered` })
}

// adding this line makes easy to test parallel requests from same user
await wait(10000)

const result = await client.db("newsletter").collection("usersV1").insertOne({email:req.body.email});

res.json({ message: `user registered successfully` })
} catch (error) {
console.log(error)
res.json({ message: error.message })
}
})

app.post('/v2/register', async (req, res) => {
try {
const result = await client.db("newsletter").collection("usersV2").insertOne({email:req.body.email});
res.json({ message: `user registered successfully` })
} catch (error) {
console.error(error.message)
res.json({ message: error.message })
}
})



function wait(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, timeout);
})
}

client.connect().then(async () => {
console.log(`connected to DB`);

await client.db("newsletter").collection("usersV2").createIndex({ email: 1 }, { unique: true })

app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
})

GitHub repo:

Share this with anybody you think would benefit from this. Have any suggestions or questions? Feel free to message me on LinkedIn

We at Code B are a team of Fullstack software developers, passionate and dedicated to growing businesses for clients.

We have experience in Web Applications(Frontend and Backend), Mobile Applications(Native and Hybrid), DevOps (AWS, GCP, Azure, Digital Ocean, and Heroku), Blockchain(Solana), and UI/UX Design(Figma).

Contact us if you need any help with Software.

--

--

Riddhesh Ganatra

Software Architect, Full Stack Web developer, MEAN/MERN stack, Microservices, etc