Backend Database Queries, Indexes, and Triggers (Structured Notes)
Fetching a Single User
Goal
- Fetch one user and their profile information using a user ID.
- Endpoint example:
GET /v1/users/:userId
Query Logic
- Use the same query used for fetching all users.
- Add a WHERE clause to filter by
user_id.
SQL Query
SELECT
u.*,
to_jsonb(up.*) AS profile
FROM users u
LEFT JOIN user_profiles up
ON u.id = up.user_id
WHERE u.id = $userId;
Parameterized Query
$userIdis a parameter placeholder.- The actual value is passed later.
Example value:
'user_uuid_value'
Why Parameterized Queries
- Prevent SQL injection attacks.
- User input is treated as data, not executable SQL.
Backend Flow
-
Frontend sends request:
GET /v1/users/:userId -
Router extracts
userId. -
Handler receives request.
-
Service layer processes logic.
-
Repository runs SQL query with parameter.
-
Database returns result.
-
Result serialized to JSON and sent to frontend.
Dynamic Filters and Sorting in APIs
Problem
APIs returning lists must support:
- Filtering
- Sorting
- Pagination
Example API:
GET /v1/users
Possible query parameters:
?page=1
&limit=10
&letter=j
&sort_by=email
&sort_order=asc
Query Parameters
Pagination Parameters
Page
- Defines which page of results to return.
Limit
- Defines number of records per page.
Default example:
page = 1
limit = 10
Filtering Example
Requirement
Filter users whose name starts with a given letter.
Query parameter:
letter=j
SQL Filter
WHERE u.full_name ILIKE :letter || '%'
where :letter = 'J'
Explanation
ILIKE→ case-insensitive match.%→ wildcard representing any characters after the first letter.
Example:
J%
Matches:
- John
- Jane
- Jack
Does not match:
- Alice
- Bob
Sorting Results
Users may choose sorting fields.
Allowed fields:
emailfull_namecreated_at
SQL Example
ORDER BY u.email DESC
Dynamic Sorting
Parameters:
sort_by=email
sort_order=desc
Example SQL structure:
ORDER BY :sort_by :sort_order
Default values if none provided:
sort_by = created_at
sort_order = desc
Pagination
When returning lists, pagination is required.
SQL uses:
OFFSETLIMIT
Example Query
LIMIT :limit
OFFSET :offset
Example
If:
limit = 10
page = 2
Offset:
offset = (page - 1) * limit
So:
offset = 10
Complete Query Example
SELECT
u.*,
to_jsonb(up.*) AS profile
FROM users u
LEFT JOIN user_profiles up
ON u.id = up.user_id
WHERE u.full_name ILIKE :letter || '%'
ORDER BY :sort_by :sort_order
LIMIT :limit
OFFSET :offset;
Creating a User (POST API)
Endpoint
POST /api/users
SQL Query
INSERT INTO users (
email,
full_name,
password_hash
)
VALUES (
:email,
:full_name,
:password_hash
)
RETURNING *;
Explanation
- Inserts new user.
- Returns created row.
Example result:
id
email
full_name
password_hash
created_at
updated_at
Updating a User Profile
Endpoint
PATCH /api/users/:userId
Partial Updates
User may update:
- bio
- phone
- avatar_url
Not all fields must be present.
Backend logic:
- Only update fields provided.
Example SQL Update Query
UPDATE user_profiles
SET
bio = :bio,
phone = :phone
WHERE user_id = :userId
RETURNING *;
Result
Updated row returned.
Updating updated_at Field
Problem
Whenever a row is updated, updated_at should change.
Example:
created_at = first creation time
updated_at = last modification time
Manual Solution
Update field manually in every query.
Example:
updated_at = CURRENT_TIMESTAMP
Problem
- Must remember in every update query.
Database Triggers
Solution
Use triggers.
What is a Trigger
A trigger is a database function that runs automatically when:
- INSERT
- UPDATE
- DELETE
events occur.
Trigger Function Example
Function updates updated_at.
Pseudo SQL:
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
Meaning:
- Whenever row updates
- Set
updated_atto current timestamp.
Trigger Setup
Create trigger on tables:
- users
- user_profiles
- projects
- tasks
Trigger runs:
BEFORE UPDATE
Purpose:
- Automatically update timestamp.
Result
Whenever a row is updated:
updated_at automatically changes
No application code required.
Database Indexes
What is an Index
Indexes improve query performance.
Analogy
Like index section of a book.
Example:
| Chapter | Page |
|---|---|
| Chapter 1 | 10 |
| Chapter 2 | 25 |
Instead of reading whole book, jump directly to page.
Problem Without Index
When querying:
SELECT * FROM tasks WHERE id = '123'
Database performs:
Sequential Scan
Process:
- Check first row
- Check second row
- Check third row
- Continue until match found
If table has:
1 million rows
Query becomes slow.
How Index Works
Index creates lookup table. Stored in one place and give direct access.
Example structure:
| id | row_location |
|---|---|
| 1 | disk_address |
| 2 | disk_address |
| 3 | disk_address |
Database can:
- Find id instantly
- Jump to row location
- Return data
Much faster.
Indexed Fields
Indexes are useful when fields appear in:
WHEREclausesJOINconditionsORDER BYclauses
Example Indexes
Email Index
CREATE INDEX idx_users_email
ON users(email);
Used when searching user by email.
Created At Index
CREATE INDEX idx_users_created_at
ON users(created_at DESC);
Optimizes sorting users by creation time.
Project Tasks Query
Fetching tasks for a project:
SELECT * FROM tasks
WHERE project_id = ?
Index required:
CREATE INDEX idx_tasks_project_id
ON tasks(project_id);
Assigned Tasks
Fetching tasks assigned to a user.
SELECT * FROM tasks
WHERE assigned_to = ?
Index:
CREATE INDEX idx_tasks_assigned_to
ON tasks(assigned_to);
Index Tradeoffs
Indexes improve read performance.
But add overhead for:
- INSERT
- UPDATE
- DELETE
Because index must also update.
Example:
Insert row
Update index
When to Create Indexes
Create index when:
- Field appears in JOIN
- Field appears in WHERE
- Field appears in ORDER BY
- Query runs frequently
Migration for Indexes and Triggers
New migration file created to:
- Add indexes
- Add triggers
Migration includes:
Index creation
Example:
CREATE INDEX idx_tasks_status
ON tasks(status);
Index sometimes creates an pverhead to store the latest data into lookup.
Trigger creation
Example:
CREATE TRIGGER update_timestamp
BEFORE UPDATE
ON users
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();
Applying Migration
Migration tool command:
dbmate up
Result:
- Indexes created
- Triggers installed
Trigger Test
Function
Update profile:
UPDATE user_profiles
SET bio = 'updated bio'
WHERE user_id = 'uuid';
Result:
updated_at automatically changes
Trigger works correctly.
Real Backend Workflow with Databases
Backend engineers typically:
- Design API
- Define database schema
- Write migrations
- Seed data
- Write queries
- Use parameterized queries
- Add indexes for performance
- Use triggers for automation
Key Responsibilities of Backend Engineers
Backend engineers mainly:
- Analyze API payloads
- Build dynamic SQL queries
- Use parameterized queries
- Optimize queries with indexes
- Manage migrations
- Maintain database performance
Practical Reality
This covers roughly:
~80% of database work for backend engineers
Remaining topics include:
- transactions
- ACID transactions
- query optimization
- distributed databases
- caching
- replication
- sharding
If you want, I can also give you a super condensed 10-minute backend database revision guide (joins + indexes + migrations + triggers) that is perfect for backend interviews and system design rounds.