How to Add IP-Based Rate Limiting to AI-Generated Websites
A few weeks ago I shipped a side project built entirely with AI coding tools — Cursor, Claude, the whole vibe coding stack. It worked great. Contact form sent emails. User registration created accounts. Everything looked polished on the surface.
Then my inbox started filling with spam. Someone had automated the contact form and fired off hundreds of messages in an hour. The worst part? There was nothing in my code stopping them.
// Problem: no rate limitingapp.post('/api/contact', (req, res) => { sendEmail(req.body.message); res.json({ success: true });});AI-generated tools are great at building UIs and wiring up APIs. They almost never add rate limiting. The reason is simple — LLMs train on tutorials and documentation that focus on happy-path features. Abuse prevention is an afterthought.
I think the key reason most vibe-coded apps skip this is that the AI hasn’t seen enough production horror stories. Let me show you three things that happen without it: inbox flood from contact form spam, fake account registrations draining your API quotas (Twilio, SendGrid, etc.), and your domain getting blacklisted by email providers.
So the solution is straightforward: rate limit by IP address on every public-facing form endpoint. Not by email, not by field value — by IP. Attackers rotate emails easily; they don’t rotate IPs as fast.
For Express apps I added express-rate-limit:
const rateLimit = require('express-rate-limit');
const contactLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 5, message: { error: 'Too many submissions' },});
const registerLimiter = rateLimit({ windowMs: 60 * 60 * 1000, max: 3, message: { error: 'Too many accounts' },});
app.post('/api/contact', contactLimiter, (req, res) => { ... });app.post('/api/register', registerLimiter, (req, res) => { ... });If you’re on Django, django-ratelimit does the same thing with a decorator. Ruby shops use rack-attack.
But middleware alone isn’t enough. I also added an infrastructure layer with Nginx — this catches abuse before it even reaches the app server:
limit_req_zone $binary_remote_addr zone=contact:10m rate=5r/h;
server { location /api/contact { limit_req zone=contact burst=2 nodelay; proxy_pass http://localhost:3000; }}The $binary_remote_addr is the key — it tracks by visitor IP, takes minimal memory (10MB handles millions of IPs). The rate=5r/h means 5 requests per hour. Adjust it to whatever makes sense for your app.
In this post, I showed why IP-based rate limiting is the single most important security fix most AI-generated websites are missing. If you used AI to build your web app, go check your form handlers right now. If there’s no limiter, add one before the spammers find you.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 express-rate-limit
- 👨💻 django-ratelimit
- 👨💻 Nginx rate limiting
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments