Node js Race Conditions
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.