Integrations → Node.js
Node.js integration
Express middleware, Fastify hook, or plain fetch(). Async, zero dependencies, works on every hosting from Vercel to self-hosted PM2.
Express middleware
Drop in zerobot.js and app.use(zerobot) before your routes:
// zerobot.js const LICENSE = process.env.ZEROBOT_KEY; const DOMAIN = process.env.ZEROBOT_DOMAIN; async function zerobot(req, res, next) { try { const ip = req.headers['cf-connecting-ip'] || req.ip; const url = 'https://api.zerobot.info/v3/openapi?' + new URLSearchParams({ license: LICENSE, ip, domain: DOMAIN, useragent: req.headers['user-agent'] || '', }); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const r = await fetch(url, { signal: controller.signal }); clearTimeout(timeout); const data = await r.json(); if (data.is_bot) { return res.status(403).send(`Blocked: ${data.reason}`); } } catch (err) { // Fail open on timeout / network error console.warn('ZeroBot unreachable, failing open', err.message); } next(); } module.exports = zerobot;
// app.js const express = require('express'); const zerobot = require('./zerobot'); const app = express(); app.use(zerobot); // screens every request app.get('/', (req, res) => res.send('Hello, human!')); app.listen(3000);
Fastify hook
const fastify = require('fastify')(); fastify.addHook('onRequest', async (req, reply) => { const url = 'https://api.zerobot.info/v3/openapi?' + new URLSearchParams({ license: process.env.ZEROBOT_KEY, ip: req.ip, domain: process.env.ZEROBOT_DOMAIN, useragent: req.headers['user-agent'] ?? '', }); const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); const data = await res.json(); if (data.is_bot) { reply.code(403).send({ error: data.reason }); } });
Next.js middleware (App Router)
// middleware.ts — runs on every request at the edge import { NextRequest, NextResponse } from 'next/server'; export async function middleware(req: NextRequest) { const ip = req.headers.get('cf-connecting-ip') || req.ip || ''; const url = 'https://api.zerobot.info/v3/openapi?' + new URLSearchParams({ license: process.env.ZEROBOT_KEY!, ip, domain: process.env.ZEROBOT_DOMAIN!, useragent: req.headers.get('user-agent') || '', }); const data = await fetch(url).then(r => r.json()).catch(() => ({})); if (data.is_bot) { return new NextResponse(`Blocked: ${data.reason}`, { status: 403 }); } }
Caching (recommended)
To avoid hitting the API on every pageview, cache the verdict per IP for 1–24 hours. Simple in-memory Map for single-instance apps, Redis for clusters:
const cache = new Map(); const TTL = 24 * 3600 * 1000; // 24h async function screen(req) { const ip = req.ip; const hit = cache.get(ip); if (hit && hit.expires > Date.now()) return hit.verdict; const data = await fetch(/* as above */).then(r => r.json()); cache.set(ip, { verdict: data, expires: Date.now() + TTL }); return data; }