Express middleware that tracks visits from AI agents and bots.
npm install ai-visit-trackerimport express from 'express'
import { aiVisitTracker } from 'ai-visit-tracker'
const app = express()
app.use(aiVisitTracker())Visit GET /ai-visits to see stats:
{
"total": 4821,
"agents": { "GPTBot": 2100, "ClaudeBot": 1200 },
"timeSeries": {
"daily": [{ "date": "2026-02-26", "count": 142 }],
"weekly": [{ "week": "2026-W08", "count": 891 }],
"monthly": [{ "month": "2026-02", "count": 3201 }]
}
}app.use(aiVisitTracker({
endpoint: '/custom-path', // default: /ai-visits
adapter: new PostgresAdapter(url), // default: SQLite (ai-visits.db)
exclude: ['/health', '/static'], // paths to skip (router-relative)
}))The HTTP path where stats are served. Defaults to /ai-visits. If you mount the middleware at a prefix (e.g. app.use('/api', aiVisitTracker())), the stats endpoint becomes /api/ai-visits.
A storage backend that extends BaseAdapter. Defaults to SQLite, which creates ai-visits.db in the process's working directory automatically.
An array of path strings to skip tracking. Paths are matched against req.path, which is router-relative. For example, if mounted at /api, use exclude: ['/health'] to skip /api/health.
import { getStats } from 'ai-visit-tracker'
// Call aiVisitTracker() first to initialise the adapter, then:
const stats = await getStats()getStats() returns the same Stats object served by the HTTP endpoint.
Implement BaseAdapter to use any storage backend:
import { BaseAdapter, Stats, QueryOptions } from 'ai-visit-tracker'
class MyAdapter extends BaseAdapter {
async migrate(): Promise<void> {
// Create tables / run schema migrations
}
async record(agent: string, path: string, timestamp: number): Promise<void> {
// Insert a visit row. timestamp is a Unix epoch integer (seconds).
}
async query(options?: QueryOptions): Promise<Stats> {
// Return aggregated stats. options.from / options.to are optional
// Unix timestamps for filtering by date range.
}
}
app.use(aiVisitTracker({ adapter: new MyAdapter() }))Agent detection is driven by the bundled agents.json file. Each key is the canonical display name shown in stats; values are substrings matched case-insensitively against the User-Agent header.
Community contributions to agents.json are very welcome — see CONTRIBUTING.md.
- Node.js >= 18
- Express >= 4.0.0
MIT