DEV Community

the bomber
the bomber

Posted on

Adding Rate Limits to a Netlify Chat Endpoint with Postgres for Palm Beach Contractors

I’m Kaleb, running Dream Futures Tech right here in Pompano Beach, Florida. When local contractors around Palm Beach County—whether they are roofers in Boca Raton or plumbers in Fort Lauderdale—ask me why their website isn't turning traffic into phone calls, it usually comes down to friction. Either the contact form is buried, or their chat widget is completely broken by spam bots submitting fake leads every three seconds.

I built a public chat and lead-capture endpoint on my own business website to test out automated SMS routing to my phone. It worked great until automated scrapers found the raw function URL. Suddenly, bots were hammering the endpoint fifty times a minute, trying to inject SQL strings and garbage URLs into my database. My serverless setup on Netlify was dispatching these requests instantly, which quickly exhausted my Postgres connection pool.

In my view, relying entirely on client-side CAPTCHAs is a losing battle because automated headless browsers just execute the JavaScript anyway. I needed a hard server-side rate limit tied to the visitor's IP address, stored directly in my Postgres database without adding heavy third-party SaaS dependencies.

Why Palm Beach Contractors Need Spam-Proof Lead Forms

Local service businesses live and die by their phone ringing. If a local AC repair company has a chat widget on their site that gets flooded with bot spam, two bad things happen. First, their actual database gets bloated with garbage data. Second, if they use automated alerts to text their dispatch crew every time a lead comes in, their crew gets woken up at 3:00 AM by Russian spam bots.

When I built my own endpoint, I wanted to ensure that a single IP address could only submit a maximum of five messages per hour. If they crossed that threshold, the server would quietly drop the request or return a standard rate-limit error.

The Architecture: Netlify Functions and Postgres

My site is hosted on Netlify, and the backend logic lives inside Netlify Functions (Node.js). For persistence, the function connects to a Postgres instance running on my home automation server via a secure tunnel.

Instead of spinning up Redis—which is overkill for my current traffic volume—I decided to use a dedicated rate-limit table in Postgres. Every time a request hits the chat endpoint, the function logs the hashed IP address and a timestamp, then counts how many requests that IP made in the last sixty minutes.

Here is the core logic I wrote for the Netlify function:

const { Client } = require('pg');

exports.handler = async (event) => {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  const clientIP = event.headers['x-nf-client-connection-ip'] || 'unknown';
  const hashedIP = require('crypto').createHash('sha256').update(clientIP).digest('hex');

  const db = new Client({
    connectionString: process.env.DATABASE_URL,
    ssl: { rejectUnauthorized: false }
  });

  try {
    await db.connect();

    // Clean up old records older than 1 hour
    await db.query('DELETE FROM rate_limits WHERE created_at < NOW() - INTERVAL \'1 hour\'' );

    // Check current count for this IP
    const res = await db.query('SELECT COUNT(*) FROM rate_limits WHERE ip_hash = $1', [hashedIP]);
    const requestCount = parseInt(res.rows[0].count, 10);

    if (requestCount >= 5) {
      await db.end();
      return {
        statusCode: 429,
        body: JSON.stringify({ error: 'Too many requests. Please call our office directly.' })
      };
    }

    // Log the current request
    await db.query('INSERT INTO rate_limits (ip_hash, created_at) VALUES ($1, NOW())', [hashedIP]);

    // Proceed with chat logic...
    await db.end();

    return {
      statusCode: 200,
      body: JSON.stringify({ success: true, message: 'Message sent!' })
    };
  } catch (err) {
    console.error(err);
    return { statusCode: 500, body: 'Internal Server Error' };
  };
}; 
Enter fullscreen mode Exit fullscreen mode

Managing Database Connections on Serverless

One thing I learned the hard way when pairing Netlify Functions with Postgres is connection management. Serverless functions spin up and down unpredictably. If you don't explicitly call db.end(), or if you open too many persistent connections during a traffic spike, you will max out your Postgres max_connections limit.

For a small business site, running a quick cleanup query (DELETE FROM rate_limits WHERE...) right inside the handler keeps the table size tiny. I don't need a background cron job to purge old logs; the garbage collection happens organically every time a real user interacts with the chat.

What Actually Worked

Implementing this simple IP hashing and Postgres counter immediately stopped the bot traffic on my site. The scrapers hit the endpoint, get blocked after five attempts, and stop wasting my server resources. More importantly, when I build or audit websites for contractors across South Florida now, I implement this exact pattern. It keeps their lead pipelines clean and ensures their real customers never get blocked by overly aggressive security firewalls.

I build sites and automations for small businesses at dreamfuturestech.com.

Top comments (0)