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

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:
Secure Headers - Your first line of defense
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:
Make sure your tsconfig.json is in the right place
Restart your TypeScript server in your IDE
Check that you've installed all the type definitions (@types/*)
Taking Security Further 🚀
Once you have this foundation in place, consider these next steps:
Authentication: Implement JWT or OAuth for secure user authentication
Environment Variables: Never hardcode secrets or configuration
CORS Protection: Configure proper Cross-Origin Resource Sharing
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!
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