[{"data":1,"prerenderedAt":337},["ShallowReactive",2],{"blog-how-to/prevent-sql-injection":3},{"id":4,"title":5,"body":6,"category":318,"date":319,"dateModified":319,"description":320,"draft":321,"extension":322,"faq":323,"featured":321,"headerVariant":324,"image":323,"keywords":323,"meta":325,"navigation":326,"ogDescription":327,"ogTitle":323,"path":328,"readTime":323,"schemaOrg":329,"schemaType":330,"seo":331,"sitemap":332,"stem":333,"tags":334,"twitterCard":335,"__hash__":336},"blog/blog/how-to/prevent-sql-injection.md","How to Prevent SQL Injection in Your App",{"type":7,"value":8,"toc":296},"minimark",[9,13,18,22,31,36,39,42,53,57,62,65,71,75,77,83,87,89,95,99,101,107,111,113,119,123,126,130,136,140,144,147,153,157,159,165,169,172,178,182,185,204,207,211,277],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,17],"h1",{"id":16},"how-to-prevent-sql-injection","How to Prevent SQL Injection",[19,20,21],"p",{},"The most important thing to get right in database security",[23,24,25,28],"tldr",{},[19,26,27],{},"TL;DR",[19,29,30],{},"Never put user input directly into SQL strings. Always use parameterized queries or an ORM like Prisma/Drizzle. If you're using template literals to build SQL queries, you're probably vulnerable.",[32,33,35],"h2",{"id":34},"what-is-sql-injection","What is SQL Injection?",[19,37,38],{},"SQL injection happens when user input becomes part of your SQL query. An attacker can manipulate the input to change what the query does.",[19,40,41],{},"Vulnerable Code",[43,44,49],"pre",{"className":45,"code":47,"language":48},[46],"language-text","// NEVER DO THIS\nconst query = `SELECT * FROM users WHERE email = '${email}'`;\n\n// What if email is: ' OR '1'='1\n// Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1'\n// This returns ALL users!\n","text",[50,51,47],"code",{"__ignoreMap":52},"",[32,54,56],{"id":55},"the-fix-parameterized-queries","The Fix: Parameterized Queries",[58,59,61],"h3",{"id":60},"nodejs-with-pg-postgresql","Node.js with pg (PostgreSQL)",[19,63,64],{},"Safe Code",[43,66,69],{"className":67,"code":68,"language":48},[46],"// Parameters are escaped automatically\nconst result = await client.query(\n  'SELECT * FROM users WHERE email = $1',\n  [email]\n);\n",[50,70,68],{"__ignoreMap":52},[58,72,74],{"id":73},"nodejs-with-mysql2","Node.js with mysql2",[19,76,64],{},[43,78,81],{"className":79,"code":80,"language":48},[46],"// Use ? placeholders\nconst [rows] = await connection.execute(\n  'SELECT * FROM users WHERE email = ?',\n  [email]\n);\n",[50,82,80],{"__ignoreMap":52},[58,84,86],{"id":85},"prisma-orm","Prisma ORM",[19,88,64],{},[43,90,93],{"className":91,"code":92,"language":48},[46],"// Prisma handles parameterization automatically\nconst user = await prisma.user.findUnique({\n  where: { email: email }\n});\n\n// Even raw queries are safe with tagged templates\nconst users = await prisma.$queryRaw`\n  SELECT * FROM users WHERE email = ${email}\n`;\n",[50,94,92],{"__ignoreMap":52},[58,96,98],{"id":97},"drizzle-orm","Drizzle ORM",[19,100,64],{},[43,102,105],{"className":103,"code":104,"language":48},[46],"// Drizzle parameterizes automatically\nconst user = await db.select().from(users)\n  .where(eq(users.email, email));\n\n// Raw SQL with sql template tag\nconst result = await db.execute(\n  sql`SELECT * FROM users WHERE email = ${email}`\n);\n",[50,106,104],{"__ignoreMap":52},[58,108,110],{"id":109},"supabase","Supabase",[19,112,64],{},[43,114,117],{"className":115,"code":116,"language":48},[46],"// Supabase client parameterizes automatically\nconst { data } = await supabase\n  .from('users')\n  .select('*')\n  .eq('email', email);\n",[50,118,116],{"__ignoreMap":52},[32,120,122],{"id":121},"what-about-dynamic-queries","What About Dynamic Queries?",[19,124,125],{},"Sometimes you need dynamic column names or table names. These can't be parameterized.",[58,127,129],{"id":128},"allowlist-pattern","Allowlist Pattern",[43,131,134],{"className":132,"code":133,"language":48},[46],"const ALLOWED_COLUMNS = ['name', 'email', 'created_at'];\nconst ALLOWED_DIRECTIONS = ['ASC', 'DESC'];\n\nfunction buildOrderBy(column, direction) {\n  // Validate against allowlist\n  if (!ALLOWED_COLUMNS.includes(column)) {\n    throw new Error('Invalid column');\n  }\n  if (!ALLOWED_DIRECTIONS.includes(direction)) {\n    throw new Error('Invalid direction');\n  }\n\n  // Safe to use because we validated\n  return `ORDER BY ${column} ${direction}`;\n}\n",[50,135,133],{"__ignoreMap":52},[32,137,139],{"id":138},"common-mistakes","Common Mistakes",[58,141,143],{"id":142},"string-interpolation","String Interpolation",[19,145,146],{},"Dangerous",[43,148,151],{"className":149,"code":150,"language":48},[46],"// Template literals DON'T protect you\nconst query = `SELECT * FROM ${table} WHERE id = ${id}`;\n\n// String concatenation is just as bad\nconst query = \"SELECT * FROM \" + table + \" WHERE id = \" + id;\n",[50,152,150],{"__ignoreMap":52},[58,154,156],{"id":155},"only-validating-some-inputs","Only Validating Some Inputs",[19,158,146],{},[43,160,163],{"className":161,"code":162,"language":48},[46],"// You parameterized email but not orderBy\nconst query = `SELECT * FROM users WHERE email = $1 ORDER BY ${orderBy}`;\n// orderBy could be: id; DROP TABLE users; --\n",[50,164,162],{"__ignoreMap":52},[58,166,168],{"id":167},"escaping-instead-of-parameterizing","Escaping Instead of Parameterizing",[19,170,171],{},"Not Recommended",[43,173,176],{"className":174,"code":175,"language":48},[46],"// Escaping is error-prone and can miss edge cases\nconst escaped = email.replace(/'/g, \"''\");\nconst query = `SELECT * FROM users WHERE email = '${escaped}'`;\n// Just use parameterized queries instead!\n",[50,177,175],{"__ignoreMap":52},[32,179,181],{"id":180},"testing-for-sql-injection","Testing for SQL Injection",[19,183,184],{},"Try these inputs in your forms/API:",[186,187,188,194,199],"ul",{},[189,190,191],"li",{},[50,192,193],{},"' OR '1'='1",[189,195,196],{},[50,197,198],{},"'; DROP TABLE users; --",[189,200,201],{},[50,202,203],{},"' UNION SELECT * FROM users --",[19,205,206],{},"If any of these cause errors or unexpected behavior, you have a vulnerability.",[32,208,210],{"id":209},"quick-reference","Quick Reference",[212,213,214,227],"table",{},[215,216,217],"thead",{},[218,219,220,224],"tr",{},[221,222,223],"th",{},"Library",[221,225,226],{},"Safe Pattern",[228,229,230,244,254,262,270],"tbody",{},[218,231,232,236],{},[233,234,235],"td",{},"pg (Postgres)",[233,237,238,239,243],{},"query('...WHERE x = $1', ",[240,241,242],"span",{},"val",")",[218,245,246,249],{},[233,247,248],{},"mysql2",[233,250,251,252,243],{},"execute('...WHERE x = ?', ",[240,253,242],{},[218,255,256,259],{},[233,257,258],{},"Prisma",[233,260,261],{},"prisma.user.findMany({ where: {...} })",[218,263,264,267],{},[233,265,266],{},"Drizzle",[233,268,269],{},"db.select().where(eq(col, val))",[218,271,272,274],{},[233,273,110],{},[233,275,276],{},"supabase.from('x').eq('col', val)",[278,279,280,286,291],"related-articles",{},[281,282],"related-card",{"description":283,"href":284,"title":285},"Complete guide to HSTS setup. Configure Strict-Transport-Security header, understand max-age, includeSubDomains, preload","/blog/how-to/hsts-setup","How to Set Up HSTS (HTTP Strict Transport Security)",[281,287],{"description":288,"href":289,"title":290},"Step-by-step guide to enabling HTTPS with SSL certificates. Learn Let's Encrypt setup, platform-specific configuration f","/blog/how-to/https-setup","How to Set Up HTTPS for Your Website",[281,292],{"description":293,"href":294,"title":295},"Step-by-step guide to securing image uploads. Image validation, resizing, EXIF metadata removal, storage security, and p","/blog/how-to/image-upload-security","How to Secure Image Uploads",{"title":52,"searchDepth":297,"depth":297,"links":298},2,[299,300,308,311,316,317],{"id":34,"depth":297,"text":35},{"id":55,"depth":297,"text":56,"children":301},[302,304,305,306,307],{"id":60,"depth":303,"text":61},3,{"id":73,"depth":303,"text":74},{"id":85,"depth":303,"text":86},{"id":97,"depth":303,"text":98},{"id":109,"depth":303,"text":110},{"id":121,"depth":297,"text":122,"children":309},[310],{"id":128,"depth":303,"text":129},{"id":138,"depth":297,"text":139,"children":312},[313,314,315],{"id":142,"depth":303,"text":143},{"id":155,"depth":303,"text":156},{"id":167,"depth":303,"text":168},{"id":180,"depth":297,"text":181},{"id":209,"depth":297,"text":210},"how-to","2026-01-20","Step-by-step guide to preventing SQL injection. Parameterized queries, ORMs, input validation, and common mistakes that leave your database vulnerable.",false,"md",null,"yellow",{},true,"Step-by-step guide to preventing SQL injection with parameterized queries.","/blog/how-to/prevent-sql-injection","[object Object]","HowTo",{"title":5,"description":320},{"loc":328},"blog/how-to/prevent-sql-injection",[],"summary_large_image","vyGEvwoiv_dX-Rbm2gm5y8462-86wlncOaJj13PxGHs",1775843918546]