Building a Secure SMS OTP API in Express.js + TypeScript in 5 Minutes (Zero Paperwork & Rate Limited)
2026-05-26T01:02:06.691Z
Building a Secure SMS OTP API in Express.js + TypeScript in 5 Minutes (Zero Paperwork & Rate Limited)
😱 Blocked by SMS Authentication Paperwork?
If you're building a side project, an e-commerce platform, or a startup MVP, chances are you'll eventually need a phone verification (SMS OTP) feature for user registration or password recovery. However, when developers start looking for an SMS API provider, they are often hit with a frustrating reality:
- Heavy Paperwork Requirements: Endless demands for business registration certificates, telecommunication carrier usage proofs, and other documents that indie hackers simply don't have.
- Pre-registered Sender IDs: Even if you submit the paperwork, you face a tedious, multi-day review process just to register the phone number sending the SMS.
- Prohibitive Costs: Expensive per-message pricing (often 30~50 KRW in the local market) that burns through a startup's limited budget.
For solo developers, freelancers, and early-stage startups trying to validate their ideas quickly, these barriers are incredibly discouraging. Isn't there a way to implement SMS verification immediately after signing up, completely free of paperwork?
In this comprehensive tutorial, we will learn how to build a highly secure SMS OTP API using Express.js and TypeScript. We'll be using EasyAuth, a developer-centric SMS API that requires absolutely zero paperwork and takes less than 5 minutes to integrate. We'll also cover crucial security implementations, specifically applying Rate Limiting to protect your API from malicious abuse, and how to manage OTP states without needing Redis.
💡 Why Choose EasyAuth?
Before diving into the code, let's briefly highlight why EasyAuth is the go-to solution for this implementation:
- Zero Paperwork: Simply sign up with your email and get your API Key instantly. No business licenses required.
- Automated Sender ID: Skip the manual review process; the system provisions and handles the sending numbers automatically.
- Affordable Pricing: Highly competitive rates at just 15~25 KRW per message—roughly half the traditional cost.
- Intuitive API Design: Forget complex setups. You only need two endpoints:
POST /sendandPOST /verify. - Free Trial: Receive 10 free SMS credits immediately upon signup to test your integration.
🛠️ Step 1: Project Setup and Dependencies
First, let's establish a solid server environment by setting up an Express.js and TypeScript project and installing the necessary packages.
# Initialize project
npm init -y
# Install core packages
# express: Web server framework
# axios: HTTP client for calling the EasyAuth API
# dotenv: Environment variable management
# express-rate-limit: Middleware to prevent excessive API requests
npm install express axios dotenv express-rate-limit
# Install TypeScript and type definitions for development
npm install -D typescript ts-node @types/express @types/node
# Generate tsconfig.json
npx tsc --init
Once installed, create a .env file in the root directory of your project and paste the API Key you generated from the EasyAuth dashboard.
PORT=3000
EASYAUTH_API_KEY=your_easyauth_api_key_here
🛡️ Step 2: Preventing Abuse with Rate Limiting
The most critical security vulnerability when exposing an SMS sending API is SMS Bombing. Because this endpoint is typically public (unauthenticated), malicious bots or users could theoretically call your /send endpoint thousands of times in a minute. This not only spams the victim but can also result in massive financial losses for you.
To mitigate this, we must configure an impregnable defense using the express-rate-limit package to block excessive requests originating from the same IP address within a specified timeframe.
🚀 Step 3: Complete Implementation Code
If you were using a traditional SMS API, you would have to generate a random 6-digit code on your backend, store it in a Redis cache or a database alongside its expiration time, and then send the message.
EasyAuth handles all of this internally. It generates the code, manages the state, and tracks expiration automatically. This stateless approach saves you from deploying and maintaining a Redis instance.
Here is the complete, production-ready server.ts code featuring both Rate Limiting and EasyAuth integration.
import express, { Request, Response } from 'express';
import rateLimit from 'express-rate-limit';
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(express.json());
// ==========================================
// 1. Rate Limiting Setup (Prevent SMS Bombing)
// ==========================================
const otpLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes window
max: 5, // Limit each IP to 5 requests per 15 minutes
message: {
success: false,
error: 'Too many requests. Please try again later.'
},
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
// EasyAuth Configuration
const EASYAUTH_API_URL = 'https://api.easyauth.kr/v1';
const EASYAUTH_API_KEY = process.env.EASYAUTH_API_KEY;
// ==========================================
// 2. Send OTP API (/send)
// We apply the rate limiter strictly to the sending endpoint.
// ==========================================
app.post('/api/otp/send', otpLimiter, async (req: Request, res: Response) => {
const { phone } = req.body;
if (!phone) {
return res.status(400).json({ success: false, error: 'Phone number is required.' });
}
try {
// Call EasyAuth /send endpoint
await axios.post(`${EASYAUTH_API_URL}/send`,
{ phone },
{ headers: { Authorization: `Bearer ${EASYAUTH_API_KEY}` } }
);
res.status(200).json({ success: true, message: 'OTP sent successfully.' });
} catch (error: any) {
console.error('OTP Send Error:', error.response?.data || error.message);
res.status(500).json({ success: false, error: 'Failed to send OTP.' });
}
});
// ==========================================
// 3. Verify OTP API (/verify)
// ==========================================
app.post('/api/otp/verify', async (req: Request, res: Response) => {
const { phone, code } = req.body;
if (!phone || !code) {
return res.status(400).json({ success: false, error: 'Phone number and code are required.' });
}
try {
// Call EasyAuth /verify endpoint
const response = await axios.post(`${EASYAUTH_API_URL}/verify`,
{ phone, code },
{ headers: { Authorization: `Bearer ${EASYAUTH_API_KEY}` } }
);
// Handle verification response
if (response.data.isValid) {
res.status(200).json({ success: true, message: 'OTP verified successfully.' });
} else {
res.status(400).json({ success: false, error: 'Invalid or expired OTP code.' });
}
} catch (error: any) {
console.error('OTP Verify Error:', error.response?.data || error.message);
res.status(500).json({ success: false, error: 'Failed to verify OTP due to server error.' });
}
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 SMS OTP Server is running on port ${PORT}`);
});
Running the Code
You can easily run the development server via your terminal:
npx ts-node server.ts
💡 Essential Tips & Best Practices
-
Rate Limiting is Mandatory, Not Optional As mentioned, because your OTP sending API is publicly accessible, you must limit the number of requests per IP using libraries like
express-rate-limit. For higher-traffic applications, consider combining this with front-end protections like Google reCAPTCHA or Cloudflare turnstile. -
Strict Environment Variable Management Your API Key represents direct financial value. Never hardcode sensitive information into your source files. Always use a
.envfile and ensure it is firmly listed in your.gitignorebefore pushing to GitHub or any public repository. -
Embrace Stateless Architecture By letting EasyAuth manage the validity and expiration of the OTPs internally, your backend becomes wonderfully stateless. This means you don't need to spin up Redis instances or map out database tables just to keep track of generated codes. Calling
/sendand/verifyis genuinely all it takes!
🎉 Conclusion
We've successfully designed and built a highly secure, rate-limited SMS OTP API using Express.js and TypeScript. As you can see from the code, implementing the actual business logic takes merely a few minutes.
As a developer, spending more time drowning in bureaucratic paperwork than writing actual code is a massive waste of your valuable resources. By adopting EasyAuth, you can eliminate these frustrations entirely.
- 🚫 Zero Paperwork: Bypass the painful business registration and certificate hurdles.
- ⚡ Lightning Fast Setup: Sign up and complete your API integration in under 5 minutes.
- 🤖 Automated Sender ID: Send messages immediately without tedious pre-registration processes.
- 💰 Affordable Pricing: Highly competitive rates at just 15~25 KRW per message, keeping your overhead low.
Whether you're hacking together a weekend toy project, freelancing for a client, or pushing out a startup MVP, try EasyAuth today. Grab your 10 free test credits and implement SMS verification the modern, painless way!
Start advertising on Bitbake
Contact Us