Applications and operations

Use prepared statements

Bind values separately from SQL so user input stays data instead of becoming executable query text.

Never build SQL by concatenating user input into the query string. Prepare the statement with placeholders, then bind each value through the database library.

Why string concatenation fails

Suppose a search box sends this value:

' OR 1=1 --

Building the query with string concatenation turns that input into SQL syntax:

const sql = `SELECT * FROM notes WHERE title = '${search}'`

The database parses ' OR 1=1 -- as code, not as a literal string. The query returns every row instead of the one the user asked for.

Placeholders keep values separate

Use a ? placeholder instead:

const sql = 'SELECT * FROM notes WHERE title = ?'
const rows = db.prepare(sql).all(search)

SQLite parses the statement once. The value travels separately and stays data, even when it contains quotes or SQL keywords.

Test it with a tricky email:

const sql = 'SELECT * FROM notes WHERE owner_email = ?'
const row = db.prepare(sql).get("o'[email protected]")

The single quote in the address is handled safely. No escaping logic on your side.

What placeholders cannot do

Placeholders represent values, not SQL structure. You cannot bind a table name, a column name, or an ASC/DESC direction. If the user picks a sort column, map the input to a small allowlist:

const sortColumns = { newest: 'created_at', title: 'title' }
const column = sortColumns[requestedSort] ?? 'created_at'
const sql = `SELECT * FROM notes ORDER BY ${column}`

Prepared statements stop SQL injection at the query boundary. They do not decide what a user may access. An authenticated user could still request another user’s note through a perfectly safe prepared statement. Keep authorization checks in your application layer.

When the same statement runs many times, reuse the prepared statement. That avoids parsing the same SQL on every call. Correctness is the main reason to prepare; performance is a bonus.

Try it: write a lookup by email using a placeholder, then pass an email containing a single quote. It should return results without changing the query shape.

Lesson completed