Why Express.js APIs Need Bot Detection
Express.js powers millions of APIs—from startup prototypes to enterprise backends. Your APIs handle sensitive operations: user authentication, payment processing, account signup, data submission, and content creation. Without bot detection, attackers can:
- Brute-force login endpoints with credential stuffing attacks (stolen username/password pairs)
- Scrape your API endpoints at scale using Python, Node.js, or Puppeteer
- Perform fake account registration to resell accounts or commit fraud
- Spam your platform with auto-generated content, comments, or reviews
- Abuse your free API tier by making millions of requests
- Test stolen credit cards on your checkout flow
Express.js is lightweight and popular with startups, which means many deployments lack bot detection from day one. This guide shows you how to add real bot detection to Express.js APIs in 5 minutes—with working code, free tier access (1,000 daily verifications), and production best practices.
How Bot Detection Works
Device.AI's bot detection API accepts an HTTP request context (IP address, headers, request body) and returns a bot risk score (0.0 = bot, 1.0 = human). You decide what to do with the score: block, challenge with CAPTCHA, rate-limit, or allow.
Unlike client-side bot detection (which requires JavaScript and browser APIs), Express.js bot detection happens server-to-server. Device.AI analyzes:
- IP reputation and geolocation
- Request headers and fingerprints
- Known bot signatures (User-Agent strings, automation tools)
- Request timing and behavioral patterns
- Proxy detection and VPN identification
Step 1: Get Your Free API Key (1 Minute)
Visit device.ai, click \"Get Free API Key\" (no signup required, no credit card), and copy your key. Your first 1,000 daily verifications are completely free.
You'll receive a key like:
dv_live_abc123def456ghi789jkl
Add it to your .env file or environment variables:
DEVICE_AI_API_KEY=dv_live_abc123def456ghi789jkl
Step 2: Install Dependencies
You only need Express and a fetch library (Node 18+ has native fetch, or use axios/node-fetch):
npm install express
npm install dotenv # For .env file loading
Or if you need an explicit HTTP client:
npm install axios
Step 3: Create Bot Detection Middleware
Create a reusable middleware that verifies requests with Device.AI:
// bot-detection.js
const fetch = require('node-fetch'); // or use native fetch in Node 18+
const DEVICE_AI_KEY = process.env.DEVICE_AI_API_KEY;
const VERIFY_URL = 'https://device.ai/v1/verify';
// Create middleware factory
function createBotDetectionMiddleware(options = {}) {
const {
threshold = 0.5, // 0.0 = bot, 1.0 = human. Block scores below threshold.
timeout = 2000, // ms to wait for verification
failOpen = true, // If true, allow request if verification fails/times out
} = options;
return async (req, res, next) => {
try {
// Call Device.AI verification endpoint
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(VERIFY_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${DEVICE_AI_KEY}`,
},
body: JSON.stringify({
ip: req.ip || req.connection.remoteAddress,
user_agent: req.get('user-agent'),
headers: req.headers,
}),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (response.ok) {
const result = await response.json();
const botScore = result.score; // 0.0-1.0
// Store score in request for later use
req.deviceAI = {
score: botScore,
isBot: botScore < threshold,
confidence: result.confidence,
};
// Block if score below threshold (low score = bot)
if (botScore < threshold) {
return res.status(403).json({
error: 'Request blocked: bot detected',
score: botScore,
});
}
} else if (!failOpen) {
// Verification API error and failOpen is false
return res.status(503).json({
error: 'Bot verification failed',
});
}
// If failOpen is true or response ok, continue to next middleware
} catch (error) {
console.error('Bot detection error:', error.message);
if (!failOpen) {
return res.status(503).json({
error: 'Bot verification unavailable',
});
}
}
next(); // Continue to route handler
};
}
module.exports = { createBotDetectionMiddleware };
Step 4: Apply Middleware to Your Express App
Option A: Protect all routes
// app.js
require('dotenv').config();
const express = require('express');
const { createBotDetectionMiddleware } = require('./bot-detection');
const app = express();
app.use(express.json());
// Apply bot detection to all routes (threshold=0.5)
app.use(createBotDetectionMiddleware({ threshold: 0.5 }));
app.post('/api/login', (req, res) => {
console.log(`Login attempt, bot score: ${req.deviceAI?.score || 'n/a'}`);
// Only bots with score >= 0.5 reach here
res.json({ success: true, token: 'abc123' });
});
app.listen(3000, () => console.log('Server running on :3000'));
Option B: Protect specific routes only
// app.js
const app = express();
app.use(express.json());
const botDetection = createBotDetectionMiddleware({ threshold: 0.5 });
// Apply bot detection only to sensitive routes
app.post('/api/login', botDetection, (req, res) => {
res.json({ success: true, token: 'abc123' });
});
app.post('/api/signup', botDetection, (req, res) => {
res.json({ success: true, userId: 'user_123' });
});
app.post('/api/checkout', botDetection, (req, res) => {
res.json({ success: true, orderId: 'order_456' });
});
// Public endpoint (no bot detection)
app.get('/api/public', (req, res) => {
res.json({ message: 'Hello, world!' });
});
Step 5: Handle Bot Scores with Adaptive Thresholds
Different endpoints need different thresholds. Create specialized middleware for sensitive operations:
// bot-detection.js (enhanced)
// Loose threshold: for public forms (comments, reviews)
const looseBotDetection = createBotDetectionMiddleware({ threshold: 0.3 });
// Standard threshold: for login and signup
const standardBotDetection = createBotDetectionMiddleware({ threshold: 0.5 });
// Strict threshold: for checkout and payments
const strictBotDetection = createBotDetectionMiddleware({ threshold: 0.7 });
module.exports = { looseBotDetection, standardBotDetection, strictBotDetection };
Now use them in your routes:
// app.js
const { looseBotDetection, standardBotDetection, strictBotDetection } = require('./bot-detection');
// Comments (public form, loose threshold)
app.post('/api/comments', looseBotDetection, (req, res) => {
// Allow more uncertainty; verify with CAPTCHA if needed
res.json({ success: true });
});
// Login (standard threshold)
app.post('/api/login', standardBotDetection, (req, res) => {
res.json({ success: true, token: 'abc123' });
});
// Checkout (strict threshold)
app.post('/api/checkout', strictBotDetection, (req, res) => {
res.json({ success: true, orderId: 'order_789' });
});
Step 6: Understand Bot Score Interpretation
| Score Range | Classification | Action |
|---|---|---|
| 0.0 - 0.3 | High confidence bot | Block immediately |
| 0.3 - 0.6 | Uncertain | Challenge (CAPTCHA, email verification) |
| 0.6 - 1.0 | Likely human | Allow request |
Adjust thresholds based on your use case. Checkout flows should be stricter (0.6-0.7) to prevent payment fraud. Comments or form submissions can be looser (0.2-0.4) to avoid false positives.
Step 7: Production Best Practices
7.1 Caching Scores
Cache verification results per IP address to reduce API calls and latency:
// bot-detection.js
const scoreCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
function getCachedScore(ip) {
const cached = scoreCache.get(ip);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.score; // Cache hit
}
return null; // Cache miss
}
function setCachedScore(ip, score) {
scoreCache.set(ip, {
score,
timestamp: Date.now(),
});
}
// Use in middleware:
const cachedScore = getCachedScore(clientIp);
if (cachedScore !== null) {
req.deviceAI = { score: cachedScore, isBot: cachedScore < threshold };
return next();
}
// If not cached, call API and cache result...
7.2 Timeout and Fail-Open
Bot detection should never break your API. Always use short timeouts and fail open (allow the request if verification fails):
// Set timeout to 2 seconds max
app.use(createBotDetectionMiddleware({
threshold: 0.5,
timeout: 2000, // 2 seconds
failOpen: true, // Allow if verification fails
}));
7.3 Rate Limiting on Quota Exceeded
When you hit the free tier limit (1,000 verifications/day), Device.AI returns HTTP 429. Implement local rate limiting:
if (response.status === 429) {
// Quota exceeded
// Option 1: Apply stricter local rate limiting
const requestCount = getLocalRequestCount(req.ip);
if (requestCount > 10) {
return res.status(429).json({ error: 'Too many requests' });
}
// Option 2: Upgrade to paid plan
console.warn('Device.AI quota exceeded. Upgrade at https://device.ai/upgrade');
}
7.4 Monitoring and Logging
Track bot detection metrics for visibility:
// middleware.js
const metrics = {
totalRequests: 0,
blockedRequests: 0,
challengedRequests: 0,
};
function recordMetric(score) {
metrics.totalRequests++;
if (score < 0.3) {
metrics.blockedRequests++;
} else if (score < 0.6) {
metrics.challengedRequests++;
}
}
// Log metrics periodically
setInterval(() => {
const blockRate = (metrics.blockedRequests / metrics.totalRequests * 100).toFixed(2);
console.log(`Bot detection: ${metrics.totalRequests} requests, ${blockRate}% blocked`);
metrics.totalRequests = 0;
metrics.blockedRequests = 0;
}, 60000); // Every minute
Step 8: Test Your Bot Detection
Verify that your implementation actually catches bots. Use curl or automated testing tools:
# Test 1: Normal curl request (looks like bot)
curl -X POST http://localhost:3000/api/login \\
-H 'Content-Type: application/json' \\
-d '{\"email\":\"test@example.com\",\"password\":\"pass\"}' \\
-v
# Test 2: Request with spoofed headers (looks more human)
curl -X POST http://localhost:3000/api/login \\
-H 'Content-Type: application/json' \\
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)' \\
-d '{\"email\":\"test@example.com\",\"password\":\"pass\"}' \\
-v
# Test 3: Puppeteer automated request (should be detected)
# node test-with-puppeteer.js
Step 9: Handling False Positives
If legitimate users are being blocked, adjust your threshold or create an allowlist:
// Allowlist specific IP ranges
const allowlist = [
'192.168.0.0/16', // Corporate network
'10.0.0.0/8', // Internal network
];
function shouldSkipVerification(ip) {
for (const range of allowlist) {
if (isIPInRange(ip, range)) {
return true;
}
}
return false;
}
// Use in middleware
if (shouldSkipVerification(req.ip)) {
return next(); // Skip verification
}
Pricing and Free Tier
| Plan | Daily Verifications | Price | Use Case |
|---|---|---|---|
| Free | 1,000 | $0 | Development, testing, small apps |
| Pay-as-you-go | Unlimited | $0.001 per verification | Production, scaling |
| Enterprise | Custom | Custom pricing | High volume, SLAs, priority support |
For 1 million daily verifications, pay-as-you-go costs ~$300/month. Upgrade anytime at device.ai/upgrade.
Common Integration Patterns
Pattern 1: Middleware + Custom Error Handling
app.post('/api/login', botDetection, (req, res) => {
if (req.deviceAI?.isBot) {
return res.status(403).json({
error: 'Bot detected',
message: 'Please try again later',
});
}
// Process login
});
Pattern 2: Challenge Instead of Block
app.post('/api/form', botDetection, (req, res) => {
const score = req.deviceAI?.score || 0.5;
if (score < 0.3) {
// Block
return res.status(403).json({ error: 'Bot detected' });
} else if (score < 0.6) {
// Challenge
return res.status(202).json({
message: 'Please verify: answer captcha',
captchaRequired: true,
});
}
// Allow
res.json({ success: true });
});
Pattern 3: Custom Score Handler Middleware
function handleBotScore(req, res, next) {
const score = req.deviceAI?.score;
if (!score) return next();
// Log all verifications
console.log(`IP ${req.ip} scored ${score}`);
// Add to response header for visibility
res.set('X-Bot-Score', score);
next();
}
app.use(handleBotScore);
Frequently Asked Questions
Can I Use This with Async/Await?
Yes. The middleware example uses async/await. For route handlers, wrap async logic in try/catch or use Express error handling middleware.
Does Device.AI Work with WebSockets?
Verify during the initial HTTP upgrade request, not on every message. Once a WebSocket connection is established, the user is already verified.
What If Device.AI Is Down?
Set failOpen: true (default) to allow requests even if verification fails. Log the incident and monitor uptime. Device.AI's API has 99.9% uptime SLA.
Can I Cache Verification Results?
Yes. Cache per IP for 5-60 minutes. Bot behavior is consistent, so caching is safe and reduces API costs.
How Long Does Verification Take?
Typical latency is 50-150ms. With caching, cache hits are instant (<1ms).
Next Steps
- Get a free API key at device.ai (1 minute, no signup)
- Copy the middleware code above into your project
- Apply to sensitive routes (login, signup, checkout)
- Test with curl and Puppeteer
- Monitor bot detection metrics
- Adjust thresholds based on false positive rate
Resources
- Device.AI API Documentation — Full API reference and integration guides
- Bot Detection for Python — Framework Guide #3 for Flask and FastAPI
- Bot Detection for React — Framework Guide #2 for React apps
- Bot Detection for Next.js — Framework Guide #1 for Next.js apps
Questions? Contact support@device.ai.