1Never concatenate inputs
If you build an insert string in Node like this: "INSERT INTO users (name) VALUES ('" + req.body.name + "')", you have created a SQL Injection vulnerability. If a user types '); DROP TABLE users; -- as their name, the database will execute it and delete your table. ALWAYS use Parameterized Queries (e.g., VALUES ($1)).
2Step-by-Step Breakdown
The Syntax. The INSERT INTO statement is used to add new rows to a table. You specify the table name, the columns you want to fill, and the values for those columns.
Single Row Insert. 'INSERT INTO users (name, email) VALUES ('Alice', 'a@b.com');'. Notice that strings require single quotes. Numbers do not.
Omitting Columns. If your table has an 'id' that auto-increments (SERIAL in Postgres), you DO NOT include it in your insert statement. The database calculates it automatically.
Default Values. If a column has a 'DEFAULT CURRENT_TIMESTAMP', you can omit it from the INSERT statement. The engine will automatically insert the current time.
Knowledge Check. If a table has an auto-incrementing id column and a created_at column with a default value of the current time, how many columns do you actually need to specify in your INSERT statement for a new user?
- →All of them, you must manually pass NULL for id
- →Only the required data columns (like name and email)
Multi-Row Inserts. You can insert 100 rows in a single command. Just separate the value groups with commas. 'VALUES ('A', 'a@b.com'), ('B', 'b@b.com');'. This is vastly faster than 100 separate inserts.
UPSERT (On Conflict). What if you try to insert an email that already exists? It crashes. Postgres has an 'UPSERT' feature: 'ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;'.
INSERT from SELECT. You can insert data by reading it from another table. 'INSERT INTO archive_users SELECT * FROM users WHERE status = 'banned';'.
Returning the ID. In Node.js, when you create a user, you usually need their new ID to send to the frontend. Use the RETURNING clause. 'INSERT INTO users (name) VALUES ('Bob') RETURNING id;'.
Summary. INSERT creates data. Bulk inserts are for speed, RETURNING is for Node.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Confirm Before Submitting Forms That Trigger Bulk INSERT Operations
A form that inserts many rows at once (e.g. a bulk CSV import mapped to a multi-row INSERT) should show a clear, keyboard-accessible summary of how many rows will be created before submission, so screen reader and keyboard users aren't surprised by a large, hard-to-undo data change.
SEO Implications
- 1
Slow INSERT Operations Delay Server Responses on Submission Forms
Inserting rows one at a time in a loop instead of using a single multi-row INSERT delays the server response a form waits on, which can hurt perceived performance metrics on pages that redirect or re-render after a successful submission.
Best Practices
Always Use Parameterized Queries for INSERT, Never String-Built SQL
Building an INSERT statement by concatenating request-body values directly into the SQL string (e.g. VALUES ('${req.body.name}')) lets an attacker inject arbitrary SQL. Use placeholders like VALUES ($1) with the driver's parameter binding instead.
Use Multi-Row INSERT for Bulk Data Instead of Looping One Row at a Time
Running INSERT INTO ... VALUES (...) inside a loop for each row means a separate network round-trip per row. Batching many rows into one INSERT with comma-separated VALUES groups is dramatically faster for bulk operations.
Frequent Bugs
A multi-row INSERT fails with a column count mismatch error on one row deep in a large batch.
Every VALUES group in a multi-row INSERT must supply the exact same number of values, in the same column order, as declared in the INSERT INTO (...) column list. A single malformed row fails the entire statement in most engines.
Code expecting the new row's auto-generated id back from an INSERT gets undefined in the application layer.
Most drivers don't return the inserted row by default. In Postgres, add a RETURNING id (or RETURNING *) clause to the INSERT statement so the new row's data comes back in the same round trip, instead of running a second SELECT.
Real-World Examples
Safely Inserting a New User with Parameterized Values
A signup endpoint needed to insert a new user record from untrusted form input without exposing the app to SQL injection, while immediately getting back the new user's id for the response.
-- Using parameterized placeholders, never string concatenation
const result = await client.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id;',
[req.body.name, req.body.email]
);
const newUserId = result.rows[0].id;