Your Code in the Crosshairs: Why TypeScript Security Is Not an Option, But a Necessity
\n\nHave you ever wondered what happens when a user enters into the search field on your site not just "Python books," but something like '; DROP TABLE users; --? If your TypeScript backend is not ready for such a turn, you risk not just getting an error, but losing your entire database. Sounds like a horror movie script? Welcome to the real world of web development, where TypeScript security is not a trendy feature, but basic code hygiene.
In this article, we won't be giving boring lectures. We'll break down two main enemies of any web application: SQL injections and XSS (Cross-Site Scripting). You'll learn how hackers attack your projects, and most importantly, how to turn your TypeScript code into an impregnable fortress using real examples and best practices.
\n\nSQL Injections: How Attackers "Talk" to Your Database
\n\nImagine your application is a polite concierge who fulfills any guest's request. If a guest asks: "Show room No. 5," the concierge shows it. But what if the guest asks: "Show all rooms and give me the safe keys"? Without verification, the concierge will do that too. SQL injection works exactly like this: an attacker "embeds" their commands into regular user input.
\n\nLet's say you have a simple query to search for a user by name:
\n\n// ❌ DANGEROUS: vulnerable code\nconst userName = req.body.username; // user entered "admin"\nconst query = `SELECT * FROM users WHERE name = '${userName}'`;\ndb.execute(query);\n\nLooks harmless? Now imagine the user enters: admin' OR '1'='1. The query becomes:
SELECT * FROM users WHERE name = 'admin' OR '1'='1'\n\nCongratulations! You've just handed over the list of all system users to an attacker. And if they want to delete something? Easy: admin'; DROP TABLE users; -- — and your database is gone.
How to Protect Yourself: Parameterized Queries Are Your Shield
\n\nThe main rule for protection from SQL injections in TypeScript: never insert user data directly into a query string. Use parameterized queries (prepared statements). They separate SQL code and data, and the database treats user input exclusively as data, not as commands.
\n\nExample with the pg library (for PostgreSQL):
// ✅ SAFE: parameterized query\nimport { Pool } from 'pg';\n\nconst pool = new Pool({\n connectionString: process.env.DATABASE_URL\n});\n\nasync function getUserByName(userName: string): Promise<any> {\n const query = 'SELECT * FROM users WHERE name = $1';\n const result = await pool.query(query, [userName]);\n return result.rows;\n}\n\n// Usage\nconst userInput = req.body.username; // even if it's "admin' OR '1'='1"\nconst user = await getUserByName(userInput);\n// The database will treat this as a search for a user with that strange name\nconsole.log(user); // [] — nothing found\n\nThe same principle works with other ORMs, for example, Prisma or TypeORM. They use parameterized queries by default, making TypeScript security a built-in feature.
\n\n// ✅ SAFE: using Prisma\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\n\nasync function findUser(name: string) {\n return prisma.user.findUnique({\n where: { name: name } // Prisma escapes input data itself\n });\n}\n\nXSS Attacks: When Your Site Starts Broadcasting Someone Else's Code
\n\nIf SQL injections hit the database, then XSS (Cross-Site Scripting) attacks your users. Imagine that an attacker