Building a Secure API: A Beginner-Friendly Guide with Express and TypeScript

Abigeal AfolabiAbigeal Afolabi
6 min read

Security isn't something you bolt on later—it's something you build in from the start. In this guide, I'll walk you through creating a secure API with Express and TypeScript, focusing on the practices I wish someone had taught me when I was just starting out.

The Security-First Mindset 🛡️

Let's be honest—most tutorials focus on getting something working quickly, with security as an afterthought. But in the real world, that approach can lead to disaster.

When I first started coding, I made the mistake of thinking, "I'll add security later." Later never came, and I learned my lesson the hard way.

Today, we're doing it right from the beginning.

What We'll Build 🏗️

We're creating a simple but secure API using:

  • Express.js for our server

  • TypeScript for type safety

  • Security middleware that actually matters

Our focus will be on two critical but often overlooked aspects:

  1. Secure Headers - Your first line of defense

  2. Input Sanitization - Because users can't be trusted (sorry, not sorry!)

Setting Up Our Project Structure 📁

First, let's organize our code in a way that makes security visible and maintainable:

secure-api-project/
├── src/
│   ├── app.ts                # Main application file
│   ├── routes/
│   │   └── index.ts          # API routes
│   ├── middleware/
│   │   ├── secureHeaders.ts  # Secure header middleware
│   │   └── sanitizeInput.ts  # Input sanitization middleware
│   └── tsconfig.json         # TypeScript configuration
├── package.json              # Project dependencies
└── README.md                 # Project documentation

💡 Pro tip: Keeping security middleware in its own directory makes it easy to maintain and helps remind your team that security is a priority.

Let's Get Coding! 👨‍💻

Step 1: Project Initialization

Fire up your terminal and let's create our project:

mkdir secure-api-project
cd secure-api-project
npm init -y

Step 2: Installing Dependencies

Time to add the packages we need:

npm install express helmet express-validator
npm install --save-dev @types/express @types/helmet @types/node typescript ts-node

Here's what each package brings to our security party:

  • express: Our web framework

  • helmet: Adds critical security headers with minimal effort

  • express-validator: Keeps malicious input at bay

Step 3: The Main Application File

Let's create our src/app.ts file:

import express from 'express';
import secureHeaders from './middleware/secureHeaders';
import routes from './routes/index';

const app = express();

// Apply secure headers middleware
app.use(secureHeaders);

// Parse JSON bodies
app.use(express.json());

// Use our defined routes
app.use('/api', routes);

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`🚀 Server secured and running on port ${PORT}`);
});

💡 Why this matters: Notice how we apply the security middleware before we parse any JSON. This order is important—it ensures requests are checked before we process them.

Step 4: Implementing Secure Headers

Create src/middleware/secureHeaders.ts:

import helmet from 'helmet';
import { Request, Response, NextFunction } from 'express';

const secureHeaders = (req: Request, res: Response, next: NextFunction) => {
    helmet()(req, res, next);
};

export default secureHeaders;

This tiny piece of code does a lot of heavy lifting! Helmet sets headers that protect against:

  • Cross-site scripting (XSS) attacks

  • Clickjacking

  • MIME type sniffing vulnerabilities

  • And much more!

It's like installing a security system for your API with just a few lines of code.

Step 5: Input Sanitization - Trust No One

Create src/middleware/sanitizeInput.ts:

import { body, validationResult } from 'express-validator';
import { Request, Response, NextFunction } from 'express';

export const sanitizeInput = [
    body('name').trim().escape(),
    body('email').isEmail().normalizeEmail(),
    (req: Request, res: Response, next: NextFunction) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }
        next();
    },
];

⚠️ Reality check: A surprising number of security breaches happen because developers trust user input. Don't be that developer!

This middleware ensures that any data coming into your API is cleaned and validated. It's like having a bouncer at the door, checking IDs before letting anyone in.

Step 6: Creating API Routes

Create src/routes/index.ts:

import { Router } from 'express';
import { sanitizeInput } from '../middleware/sanitizeInput';

const router = Router();

// Get user data
router.get('/user', (req, res) => {
    res.json({ message: 'User data retrieved successfully' });
});

// Create a new user - with sanitization!
router.post('/user', sanitizeInput, (req, res) => {
    res.status(201).json({ 
        message: 'User created successfully',
        user: req.body // Now safe to use!
    });
});

export default router;

See how we applied our sanitizeInput middleware to the POST route? That's where security meets functionality.

Step 7: TypeScript Configuration

Create src/tsconfig.json:

{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

TypeScript itself is a security feature—catching type errors before they become runtime vulnerabilities.

Testing Your Secure API ✅

Ready to see your work in action? Run:

npx ts-node src/app.ts

Now open a new terminal and test your endpoints:

# Test GET endpoint
curl http://localhost:3000/api/user

# Test POST endpoint with valid data
curl -X POST -H "Content-Type: application/json" \
  -d '{"name":"Dev Newbie","email":"dev@example.com"}' \
  http://localhost:3000/api/user

# Test sanitization with invalid email
curl -X POST -H "Content-Type: application/json" \
  -d '{"name":"Hacker","email":"not-an-email"}' \
  http://localhost:3000/api/user

That last request should fail with a validation error—exactly what we want!

Debugging Common Issues 🐛

Even the best developers run into issues. Here are some common ones:

"Module not found" errors

Error: Cannot find module './middleware/secureHeaders'

Solution: Double-check your file paths. Remember that TypeScript is case-sensitive!

Middleware not working

If your security measures don't seem to be working, check the order of your middleware. Middleware order matters in Express!

// CORRECT ORDER
app.use(secureHeaders); // First, secure the connection
app.use(express.json()); // Then, parse the body
app.use('/api', routes); // Finally, route the request

TypeScript compilation errors

If you're seeing TypeScript errors that don't make sense, try:

  1. Make sure your tsconfig.json is in the right place

  2. Restart your TypeScript server in your IDE

  3. Check that you've installed all the type definitions (@types/*)

Taking Security Further 🚀

Once you have this foundation in place, consider these next steps:

  1. Authentication: Implement JWT or OAuth for secure user authentication

  2. Environment Variables: Never hardcode secrets or configuration

  3. CORS Protection: Configure proper Cross-Origin Resource Sharing

  4. Rate Limiting: Prevent brute force and DOS attacks

Why This All Matters 💭

Security isn't just a checkbox—it's a mindset. By incorporating these practices from the beginning of your development journey, you're building habits that will serve you well throughout your career.

The code we've written today isn't just a tutorial exercise—it's a foundation you can build upon for real-world projects. Every line has a purpose, and that purpose is protecting your API, your data, and ultimately your users.

Remember, in the world of web development, security isn't something you add later—it's something you build in from day one.


What security practices do you incorporate in your APIs? Drop a comment below—I'd love to hear about your experiences!

[@arbythecoder]

Follow me for more practical guides on secure development for beginners!


1
Subscribe to my newsletter

Read articles from Abigeal Afolabi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abigeal Afolabi
Abigeal Afolabi

🚀 Software Engineer by day, SRE magician by night! ✨ Tech enthusiast with an insatiable curiosity for data. 📝 Harvard CS50 Undergrad igniting my passion for code. Currently delving into the MERN stack – because who doesn't love crafting seamless experiences from front to back? Join me on this exhilarating journey of embracing technology, penning insightful tech chronicles, and unraveling the mysteries of data! 🔍🔧 Let's build, let's write, let's explore – all aboard the tech express! 🚂🌟 #CodeAndCuriosity