SQL asks for a set of rows
JavaScript often starts with a list and loops over it. SQL describes the rows you want, and PostgreSQL chooses how to find them.
Suppose Maya opens the task list for an Acme project. The query needs both the project and the authenticated workspace scope.
-- Illustrative SQL. Values are supplied separately.
SELECT id, title, status, version
FROM tasks
WHERE workspace_id = $1
AND project_id = $2
ORDER BY created_at DESC, id DESC
LIMIT $3;
WHERE restricts the rows. ORDER BY defines their order. LIMIT bounds how many rows the database returns. Without an explicit order, PostgreSQL does not promise the order your frontend expects.
The workspace condition is a security requirement. The project condition narrows the page. Even when project IDs are globally unique, keeping the workspace condition makes the operation's tenant scope explicit.
Values are parameters
The $1 placeholders do not mean string replacement. The driver sends query structure and values separately. PostgreSQL treats an attacker-controlled title as a value rather than executable SQL.
Building a query by interpolating an input string can turn that string into SQL syntax. Parameterization prevents that category of injection. It does not verify permissions, and it does not make every query efficient.
Column names and sort directions are query structure, so a user-controlled sort option must map to an allowed set. Treating arbitrary input as a column name defeats the protection that value parameters provide.
Joins combine related facts
A task stores its assignee identifier. A join can retrieve the assignee's display information without placing a copied name on every task row. This avoids stale copies after a person updates their profile.
An inner join removes rows with no match. A left join keeps the task and supplies NULL for the missing joined values. That difference matters when an unassigned task must remain visible.
A frequent mistake is fetching all workspace tasks and filtering them in JavaScript. The database sends unnecessary data, the API uses more memory, and sensitive rows can escape before the filter runs.
Does a parameterized query prevent cross-tenant access?
No. It keeps values separate from SQL syntax. The query still needs the correct workspace predicate and an authorized workspace scope.
SQL describes the requested set. Parameterization protects the syntax, while scoped predicates protect which records enter that set.
Read api/src/tasks.ts Read api/src/db.ts