Skip to content

Instantly share code, notes, and snippets.

@Gaurav8757
Created May 31, 2026 08:57
Show Gist options
  • Select an option

  • Save Gaurav8757/d1f3420aa815876665b65ccc389f67df to your computer and use it in GitHub Desktop.

Select an option

Save Gaurav8757/d1f3420aa815876665b65ccc389f67df to your computer and use it in GitHub Desktop.
OTP Verify | OTP Timer With Redis TTL
// Best approach:
// OTP send API se initial TTL le lo.
// Frontend me local countdown chalao.
// Jab page refresh ho ya component remount ho tab TTL API hit karo.
// Backend API
// send otp
app.post("/otp/send", async (req, res) => {
const { phone } = req.body;
if (!phone) {
return res.status(400).json({ error: "Phone number is required" });
}
const otpCode = generateOtp();
await redisClient.set(otpKey(phone), Number(otpCode), "EX", 100); // valid only for 60 seconds
const ttl = await redisClient.ttl(otpKey(phone));
return res.status(200).json({ success: true, data: `OTP for ${phone} is ${otpCode} and TTL is ${Math.floor(ttl)} seconds` });
});
// verify otp
app.post("/otp/verify", async (req, res) => {
const { phone, otp } = req.body;
// if phone and otp are not found, return error
if(!phone || !otp){
return res.status(400).json({ error: "Phone and OTP are required!" });
}
// if OTP is not found, return error
if(!Boolean(await redisClient.exists(otpKey(phone)))){
return res.status(404).json({ success: false, data: "OTP not found!" });
}
// get the stored OTP
const storedOtp = await redisClient.get(otpKey(phone));
// if OTP is null, return error
if(!storedOtp){
return res.status(404).json({ success: false, data: "OTP not found!" });
}
// if OTP is not matching, return error
if(storedOtp !== otp){
return res.status(404).json({ success: false, data: "Invalid OTP!" });
}
await redisClient.del(otpKey(phone));
return res.status(200).json({ success: true, data: "OTP verified successfully!" });
});
// ttl
app.get("/otp/:phone/ttl", async (req, res) => {
const { phone } = req.params;
if(!phone){
return res.status(400).json({ error: "Phone number is required!" });
}
const ttl = await redisClient.ttl(otpKey(phone));
return res.status(200).json({ success: true, data: ttl });
});
// Frontend
import { useEffect, useState } from "react";
export default function OtpTimer() {
const [timeLeft, setTimeLeft] = useState(0);
// OTP send API se initial TTL le lo.
const sendOtp = async () => {
const res = await fetch("/otp/send", {
method: "POST",
});
const data = await res.json();
setTimeLeft(data.ttl);
};
// Jab page refresh ho ya component remount ho tab TTL API hit karo.
useEffect(() => {
const getTTL = async () => {
const res = await fetch(`/otp/ttl/${phone}`);
const data = await res.json();
setTimeLeft(data.ttl);
};
getTTL();
}, []);
// Frontend me local countdown chalao
useEffect(() => {
if (timeLeft <= 0) return;
const interval = setInterval(() => {
setTimeLeft((prev) => prev - 1);
}, 1000);
return () => clearInterval(interval);
}, [timeLeft]);
return (
<>
<button onClick={sendOtp}>Send OTP</button>
<p>
Remaining: {Math.floor(timeLeft / 60)}:
{(timeLeft % 60).toString().padStart(2, "0")}
</p>
</>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment