Your API is Cute, But Where's the Real Backend? - Part 2 🤔
In Part 1, we ran the kitchen - took orders, routed them to the right chefs, and served our hungry users.
But now it's time to secure the place.
Because a solid backend without security is like a fancy restaurant with the doors wide open and your secret sauce recipe taped to the front window.
Soo... Let Me Cook 👨🏻🍳 !!!
🧾 1. JWTs - Who's Allowed In?
A guy in a stained T-shirt walks through the back door claiming he's "new kitchen staff."
No apron. No ID. Just vibes 🤨.
Would you:
a) Hand him the spatula? 🍳
b) Call the manager? 📞
c) Chuck him out? 🚫
In a restaurant, identity matters. So does it in your backend.
Think of JWTs (JSON Web Tokens) as guest passes. Without one, you're not getting a seat, let alone the special menu.
🧂 How it works:
// User logs in → you give them a signed token
{
"userId": "12345",
"role": "admin",
"exp": 1724102400
}
// Client sends it in every request
Authorization: Bearer <token>
// You verify it on every incoming request
🔐 No token, no service. Go grab a reservation.
🧾 2. Validate Everything - Trust No Ingredient
An order slip comes in:
"One 🍔 burger with alert("HAH!") sauce and a DROP TABLE fries 🍟💣."
Your chef pauses. This ain't food - it's sabotage 🧨.
Never trust raw input. That's how you end up with broken dishes and food poisoning (aka bugs and exploits) 🤢.
Why I use Zod:
- TypeScript-friendly.
- Clear schema definitions.
- Instant, helpful error messages.
import { z } from "zod";
const orderSchema = z.object({
itemId: z.string().uuid(),
quantity: z.number().min(1),
instructions: z.string().optional(),
});
const result = orderSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
🍅 Bad inputs don't get past the kitchen door.
🧾 3. Request Signatures - Is It Really from the Delivery Partner?
You get a phone call 📞:
"Yeah, send 20 pizzas🍕 to table 12. No, you don't know me. But trust me 🚩."
Sketchy, right?
Stripe, Razorpay, or Twilio send data to your backend. But how do you know it's actually from them?
You verify the signature:
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
✉️ Fake orders? Not in my kitchen.
🧾 4. Rate Limiting - No One Gets to Hog the Buffet
Imagine an all-you-can-eat buffet 🍽️. Everyone's welcome - but if someone starts hogging the French fries🍟 tray and stuffing it into their bag 🛍️, you're gonna have a problem.
Rate limiting is your gentle-yet-firm waiter 👨🍳 who says,
"Sir, you've already had 100 dumplings 🥟. Let's give others a chance."
✅ Keeps your kitchen running smooth.
✅ Stops bad actors from overwhelming the chef.
Protect your kitchen from spammy bots and overly enthusiastic users.
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
});
app.use('/api/', limiter);
🍽️ One plate per person, please.
🧾 5. Sanitization - Clean Those Ingredients
Would you toss dirty, unwashed vegetables 🥬🥕 straight into a customer's meal?
No?
Then don't throw raw user input into your database or HTML either.
This is your sous chef 👨🍳 rinsing every tomato 🍅, checking for moldy spinach 🧫, and making sure no one's dropped a cockroach 🪳 in the curry 🍛 (aka <script>alert(1)</script>
).
✅ Protects against XSS
✅ Keeps the dish (and your app) safe to serve
Don’t let dangerous code sneak in through user input.
- Use
xss
,sanitize-html
, or ORM-level sanitization. - Always escape stuff shown on the frontend.
- Never directly plug user input into database queries.
import xss from 'xss';
const cleanComment = xss(req.body.comment);
👨🍳 No one wants <script>alert(1)</script>
in their lasagna.
🧾 6. Secure Headers - Lid on the Dish
You just made the perfect Curry 🍛.
You don't leave it sitting open near a window where flies and stray cats can jump in 🤢.
You cover it.
Secure headers are the cling film, the plastic lid, the "do not touch" sign.
They tell browsers how to treat your content safely and defensively.
✅ Deflects common exploits
✅ Gives your frontend a safety net
Add security-focused headers to protect your frontend from common attacks (XSS, clickjacking, etc.).
- Use
helmet
for Express - Use
next-secure-headers
for Next.js
import helmet from 'helmet';
app.use(helmet());
🛡️ It's the cling wrap on your freshly made ravioli.
🧾 7. Secrets & Config — Lock the Pantry
Would you shout your secret sauce recipe across the dining room while the health inspector's watching? 🤷♂️
Exactly.
Secrets (like API keys and DB passwords) don't belong in your codebase.
Use:
-
.env
files (but keep them out of Git) - Secret managers (like AWS Secrets Manager, Vercel's UI, etc.)
JWT_SECRET=supersecretkey
STRIPE_KEY=sk_test_abc123
🗝️ Would you leave your house keys taped to the door? Didn't think so.
👨🍳 Next Up: Observability, Logging, and Monitoring (Part 3)
The orders are flowing. The doors are locked.
But what if the stove catches fire?
In Part 3, we'll dig into:
- 📊 Observability: see what's cooking (and what's burning)
- 🧾 Structured logging with Winston & Pino
- ⚠️ Monitoring, alerts, and catching bugs in real-time
Because a good chef watches every plate leave the kitchen — and knows when something's burning.
Let me know in comments if your API is now a little more secure 🍜
The kitchen's locked. Now it's time to install smoke alarms and CCTV.
See you in Part 3 🔥📊
Top comments (2)
Great analogy—locking the doors before serving dishes really drives the point home. On Windows, I’ve been using ServBay to spin up PHP, APIs, databases, and HTTPS in a click—makes testing security layers like JWTs, input validation, and headers way easier.
Glad you liked the post - ServBay sounds like a super handy setup for spinning things up quickly 🙌.