FULL-STACK / MARCH – APRIL 2026
ThinkBoard
A notes app with a rate-limited Express API.
Built on my own.
- MY ROLE
- Solo project
- KEY RESULT
- 50 requests per 60 seconds, then HTTP 429
- DECISION
- Use a sliding-window limiter, then question the shared key when fairness broke down.
- EVIDENCE
- Interactive reproduction on this site plus the exact middleware source.
How a request flows
- React + Tailwind
- Rate limiter
- Express routes
- MongoDB
Can you lock out another visitor?
This is the same sliding-window rule ThinkBoard runs in Redis, scaled to 10 requests per 10 seconds. Start with the shared key, burst your requests, then ask another visitor to send one.
Waiting for the first request
- Responses appear here.
Why
I wanted a small app where I owned every layer, so I could follow a single request from a button click to the database and back.
What I built
- 01
Built the React interface and Express API for full create, read, update, and delete on notes.
- 02
Added Upstash Redis sliding-window rate limiting as Express middleware: 50 requests per 60 seconds, then HTTP 429.
- 03
Handled 429 in the interface with a dedicated rate-limited screen instead of a generic error.
- 04
Required title and content in both the form and the Mongoose schema, and added toast messages so every create, edit, and delete reports its result.
The code
// config/upstash.js: 50 requests per 60 seconds, sliding window
const rateLimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(50, "60 s"),
});
// middleware/rateLimiter.js
const ratelimiter = async (req, res, next) => {
try {
const { success } = await ratelimit.limit("my-rate-limit");
if (!success) {
return res.status(429).json({
message: "Too many requests, please try again later",
});
}
next();
} catch (error) {
next(error);
}
};Outcome
A working notes app where every action gives clear feedback, and the API protects itself (and the Upstash free tier) from bursts of requests.
The trade-off
The limiter uses one fixed key, so all visitors share a single 50-request budget. That kept the code to a few lines and protects the free-tier quota, but one busy client can lock everyone out. Keying by IP address or user would isolate them, at the cost of more Redis keys.
What I learned
A rate limiter is only as fair as its key. Choosing what to count matters as much as the limit itself.
What I'd fix next
Key the limiter by IP, and return 400 with the field name when Mongoose validation fails. Right now a missing title falls through to a generic 500.
Go deeper
What a sliding-window rate limiter actually counts4 min readBuilt with
- React
- JavaScript
- Node.js
- Express
- MongoDB
- Redis
Working on something like ThinkBoard?
Tell me what you're building and I'll reply within two business days.