DEV Community

Cover image for How I Finally Secured My impextech Node.js API (Without Losing My Mind to TypeScript) πŸ›‘οΈ
Dillibe Chisom Okorie
Dillibe Chisom Okorie

Posted on

How I Finally Secured My impextech Node.js API (Without Losing My Mind to TypeScript) πŸ›‘οΈ

Let's be honest: building an API that "works on my machine" is a great feeling. But moving from an API that simply returns data to one that is actually secure, type-safe, and documented? That’s a whole different beast.

I am currently transitioning from medicine (I'm an MD) to backend engineering, and I’ve been building a gadget e-commerce engine called impextech. After finishing my unit tests and getting the MongoDB data layer working, I hit a wall.

My authentication logic was messy. My controllers were getting bloated. And testing my protected routes in Postman was driving me crazy.

Here is exactly how I cleaned up the mess, locked down my routes, and saved my sanity using express-jwt, TypeScript Generics, and Swagger.

1. Stopping the Copy-Paste Madness (JWT Middleware)

Originally, I was manually verifying JWTs inside my route handlers. If a user wanted to add a product, I’d check the header, decode the token, check their role... you know the drill. It was repetitive and prone to errors.

I finally removed all of that out and set up a proper middleware chain using express-jwt. I created a "Gate" that automatically handles the decryption, and then built a specific "VIP Checkpoint" for my admin routes.

It looks like this:

export const isAdmin = (req: JWTRequest<TokenPayload>, res: Response, next: NextFunction) => {
    if(req.auth && req.auth.role ==='admin'){
        next();
    }
        res.status(403).json({ message: 'Access denied. Admins only.' ,success:false});   

    }
Enter fullscreen mode Exit fullscreen mode

Now, my route file is incredibly clean: router.post('/add', requireLogin, isAdmin, createProduct);

2. The TypeScript "Aha!" Moment πŸ’‘

If you write standard JavaScript, attaching user data to a request object is easy: req.user = decodedToken.

But if you use TypeScript, you know the pain I ran into next. The compiler started screaming at me because standard Express Request objects don't have an auth or user property.

The temptation to just slap an any type on the request and move on was huge. But I forced myself to dig into Generics (<T>) instead.

I defined exactly what my token payload looks like:

interface TokenPayload {
    id: string;
    email: string;
    role: string;
}
Enter fullscreen mode Exit fullscreen mode

Then, I mapped it to the request: JWTRequest<TokenPayload>.

Suddenly, my IDE knew exactly what was inside req.auth. No more undefined property errors. The compiler now enforces my role-based access before a single line of business logic executes. Getting that autocomplete popup in VS Code for req.auth.role was one of the most satisfying moments of this project.

Image illustration

3. Putting the API on a Diet (The Service Layer)

As I added more security, my controllers were getting fat. They were handling HTTP status codes, parsing body data, and trying to do database lookups.

I decided to implement a Service Layer.

I split the codebase so that my Controllers only handle the web logic (req/res), and my Services handle the heavy lifting (database queries, business rules, password hashing).

If I ever want to write a background script to update inventory, I don't have to mock an HTTP request. I can just call the Service directly. It made the whole engine drastically easier to reason about

4. Throwing out my Postman Tabs (Enter Swagger)

Writing a secure API is great, but testing protected routes blindly is a nightmare. I was getting so tired of manually copying tokens, opening Postman, pasting them into the Authorization header, and hoping I didn't miss a character.

I took the time to integrate Swagger (OpenAPI).

Instead of guessing what my endpoints need, I now have an interactive UI. I can log in once, click the little "Authorize" padlock, paste my token, and instantly test any protected route directly from my browser.

Image illustration

In medicine, we use patient charts so every doctor knows exactly what's happening. Swagger is basically the medical chart for my API. It keeps everything readable and testable.

What I Learned

Moving from "it works" to "it's structured" is painful but worth it. By combining Type-Safe middleware with auto-generated documentation, the foundation of my engine is finally solid. Next up: finishing the CRUD operations and getting this thing connected to a frontend.

How do you guys handle your route protection? Do you roll your own middleware or lean on heavier packages like Passport? Let me know in the comments

Top comments (0)