← Back to Blog
Express.jsNode.jsBot DetectionAPI SecurityBackend SecurityWeb Development

Bot Detection for Express.js: Protect Your Node.js API from Bots

·12 min read·Device.AI Engineering

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 RangeClassificationAction
0.0 - 0.3High confidence botBlock immediately
0.3 - 0.6UncertainChallenge (CAPTCHA, email verification)
0.6 - 1.0Likely humanAllow 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

PlanDaily VerificationsPriceUse Case
Free1,000$0Development, testing, small apps
Pay-as-you-goUnlimited$0.001 per verificationProduction, scaling
EnterpriseCustomCustom pricingHigh 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

  1. Get a free API key at device.ai (1 minute, no signup)
  2. Copy the middleware code above into your project
  3. Apply to sensitive routes (login, signup, checkout)
  4. Test with curl and Puppeteer
  5. Monitor bot detection metrics
  6. Adjust thresholds based on false positive rate

Resources

Questions? Contact support@device.ai.

Ready to stop bots?

Get a free API key instantly. No signup, no credit card.

Get Free API Key →