Secure Programming and Resilience (SPR) Best Practices in TypeScript
A user submitted a form with a <script> tag in the name field. The name rendered on the admin dashboard. The script executed. We had an XSS vulnerability ...
19 Apr 2024

A user submitted a form with a <script> tag in the name field. The name rendered on the admin dashboard. The script executed. We had an XSS vulnerability in production for three weeks before anyone noticed.
Security and resilience aren't features you bolt on later. They're constraints you build around from the start.
What SPR means
Secure Programming and Resilience is a set of practices that make your software resistant to attacks and graceful under failure. It covers input validation, error handling, authentication, encryption, and fault tolerance.
Think of it as defensive driving. You're not just building for the happy path. You're building for the malicious user, the flaky network, and the database that goes down at 3am.
Input validation
Never trust user input. Validate everything. Sanitize before rendering.
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
This is the first line of defense against injection attacks — SQL injection, XSS, and CSRF all start with unvalidated input. TypeScript's type system helps, but types disappear at runtime. You still need runtime validation at every system boundary.
Error handling
Catch errors. Handle them meaningfully. Don't swallow them silently. Don't crash the entire process.
try {
const data = await fetchUserData(userId);
return data;
} catch (error) {
logger.error('Failed to fetch user data', { userId, error: error.message });
throw new ServiceError('User data unavailable', { cause: error });
}
Log context — what operation failed, with what inputs, in what state. A bare console.error(error) is almost useless in production. A structured log with context is the difference between a 5-minute fix and a 5-hour investigation.
Authentication and authorization
Authentication verifies identity — who are you? Authorization verifies access — what can you do?
function authenticate(req: Request, res: Response, next: NextFunction): void {
if (!req.isAuthenticated()) {
res.status(401).json({ message: 'Unauthorized' });
return;
}
next();
}
Use established libraries (Passport.js, next-auth) instead of rolling your own. Use JWTs or sessions, not custom token schemes. Apply the principle of least privilege — give users only the access they need.
Data encryption
Sensitive data needs protection at rest and in transit.
import CryptoJS from 'crypto-js';
const encrypted = CryptoJS.AES.encrypt('sensitive-data', secretKey).toString();
const decrypted = CryptoJS.AES.decrypt(encrypted, secretKey).toString(CryptoJS.enc.Utf8);
Use HTTPS everywhere. Encrypt database fields that contain PII. Never store passwords in plain text — use bcrypt or argon2. Never commit secrets to version control.
Resilient architecture
Systems fail. Networks drop. Services go down. Your code needs to handle it without cascading failures.
async function fetchData(url: string, retries: number = 3): Promise<any> {
try {
const response = await fetch(url);
return response.json();
} catch (error) {
if (retries > 0) {
await delay(1000 * (4 - retries)); // exponential-ish backoff
return fetchData(url, retries - 1);
}
throw new Error(`Failed to fetch ${url} after multiple retries`);
}
}
Implement retries with backoff. Use circuit breakers to stop hammering a failing service. Set timeouts on every external call. Design for degraded operation — if the recommendation engine is down, show default results instead of an error page.
The trade-off
Security and resilience add code, complexity, and development time. Validation, encryption, retry logic — none of it delivers a user-visible feature. It's invisible infrastructure.
The cost of not building it is worse. A security breach damages trust. An unhandled outage loses revenue. A single XSS vulnerability can compromise every user session.
Build the defenses upfront. The cost is predictable. The cost of a breach is not.
Keep reading
- DI, SOLID, and the Fundamentals That Actually Matter
- Coding Principles for Better Code Quality
- Right Balance: Best Practices for Code Comments in TypeScript
- Best Practices for Writing Isolated Tests in TypeScript
- Meaningful Test Concepts: Best Practices for Writing Tests in TypeScript
- Open-Closed Principle (OCP) Best Practices in TypeScript: Guidelines and Examples