Applications and operations

Use prepared statements

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

8 minute lesson

~~~

Prepare SQL with placeholders, then bind each value through the database library.

Suppose a search box contains this value:

' OR 1=1 --

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

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

Use a placeholder instead:

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

The SQL text and the value now travel to SQLite separately. SQLite parses the statement as code and treats search only as data, even when it contains quotes or SQL keywords.

Placeholders can represent values, not SQL structure. You cannot bind a table name, column name, or ASC/DESC direction. If the user can choose one of those, 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 prevent SQL injection at the query boundary, but 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 and domain validation in place.

When the same statement runs repeatedly, reuse the prepared statement if your database library supports it. This can avoid parsing the same SQL over and over, but correctness and safety are the primary reasons to prepare it.

Try it: write a lookup by email using a placeholder, then pass an email containing a single quote. It should be handled as an ordinary value without changing the query.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →