On this page

MySQL Store

The MySQL store enables distributed rate limiting using your existing MySQL database. Ideal for teams running LAMP stacks, WordPress infrastructure, or any MySQL-backed application who want to add rate limiting without introducing Redis.

Installation

The MySQL store requires mysql2 as a peer dependency:

npm install mysql2
# or: pnpm add mysql2
# or: yarn add mysql2
bun add mysql2

Usage

import { hitlimit } from '@joint-ops/hitlimit'
import { mysqlStore } from '@joint-ops/hitlimit/stores/mysql'
import mysql from 'mysql2/promise'

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'secret',
  database: 'myapp'
})

app.use(hitlimit({
  limit: 100,
  window: '1m',
  store: mysqlStore({ pool })
}))
import { hitlimit } from '@joint-ops/hitlimit-bun'
import { mysqlStore } from '@joint-ops/hitlimit-bun/stores/mysql'
import mysql from 'mysql2/promise'

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'secret',
  database: 'myapp'
})

const limiter = hitlimit({
  limit: 100,
  window: '1m',
  store: mysqlStore({ pool })
})

Options

OptionTypeDefaultDescription
poolPool-A mysql2/promise Pool instance (required)
tablePrefixstring'hitlimit'Prefix for rate limit table names
cleanupIntervalnumber60000Interval (ms) to clean expired entries
skipTableCreationbooleanfalseSkip automatic table creation on startup

Schema

The store automatically creates the following InnoDB tables (prefix defaults to hitlimit):

CREATE TABLE IF NOT EXISTS hitlimit_hits (
  `key` VARCHAR(255) NOT NULL PRIMARY KEY,
  count INT NOT NULL DEFAULT 1,
  reset_at BIGINT NOT NULL
) ENGINE=InnoDB

CREATE TABLE IF NOT EXISTS hitlimit_bans (
  `key` VARCHAR(255) NOT NULL PRIMARY KEY,
  expires_at BIGINT NOT NULL
) ENGINE=InnoDB

CREATE TABLE IF NOT EXISTS hitlimit_violations (
  `key` VARCHAR(255) NOT NULL PRIMARY KEY,
  count INT NOT NULL DEFAULT 1,
  reset_at BIGINT NOT NULL
) ENGINE=InnoDB

MySQL vs PostgreSQL

AspectMySQLPostgreSQL
AtomicityINSERT ON DUPLICATE KEY UPDATE + LAST_INSERT_ID()ON CONFLICT DO UPDATE + named prepared statements
CleanupTimer-based (DELETE WHERE expired)Timer-based (DELETE WHERE expired)
Drivermysql2pg
Extra infraNone (use existing DB)None (use existing DB)
CompatibilityMySQL 5.7+, MariaDB, PlanetScalePostgreSQL 12+

Characteristics

  • Persistence: Full MySQL/InnoDB durability (redo log, replication)
  • Scalability: Shared across all server instances
  • Atomic: INSERT ON DUPLICATE KEY UPDATE with LAST_INSERT_ID() ensures race-condition-free increments
  • Cleanup: Periodic timer deletes expired rows (configurable interval)
  • Dependencies: Requires mysql2 driver (>=3.0.0)

When to Use

  • Teams already running MySQL in production
  • LAMP stack and WordPress-ecosystem applications
  • PlanetScale, AWS RDS, or MariaDB users
  • When you want distributed rate limiting without adding Redis or Postgres
  • MySQL-first architectures with existing connection pools