Start learning free

LESSON 129 OF 173 · BACKEND: SPEED

Insert Many at Once

Practice

A supplier sends the shop a list of new products. One POST /products per product costs one request and one query each, and every INSERT is saved on its own. Saving is the slow part: the database waits until the disk has really stored the data. A batch sends the whole list in one request, and the shop saves it in one go.

Prepare once, run many times: a prepared statement can run as many times as you like, with new values each time. Each row is an array, with its values in the order of the ? marks, and .run(...row) spreads it into the values of .run. Between BEGIN and COMMIT, the rows wait together in one transaction and are saved together. Here it adds two lines to order 4 at once, 2 mugs and 1 lamp:

const items = [[4, 1, 2], [4, 3, 1]];
const statement = db.prepare("INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)");
db.exec("BEGIN");
for (const item of items) {
  statement.run(...item);
}
db.exec("COMMIT");

All or nothing: if one row is refused, like a product with no price, whose item.price is undefined, .run throws an error when the loop reaches that row. The rows before it are inserted but not saved yet: they wait in the open transaction, and db.exec("ROLLBACK") throws them all away. The client can fix its list and send it again without making doubles.

GET /products  →  200  [{"id": 1, "name": "Mug", "price": 12}, …, {"id": 8, "name": "Gift card", "price": 50}]
POST /products/batch  [{"name": "Cup", "price": 8}, {"name": "Bowl", "price": 11}, {"name": "Vase", "price": 18}]
    →  201  {"added": 3}
GET /debug/queries  →  200  {"queries": 2}   (1 for the first GET /products, 1 for the whole batch)
GET /products  →  200  [… the 8 products …, {"id": 9, "name": "Cup", "price": 8}, {"id": 10, "name": "Bowl", …}, {"id": 11, "name": "Vase", …}]
POST /products/batch  [{"name": "Tray", "price": 6}, {"name": "Clock"}]
    →  400  {"error": "Each product needs a name and a price"}
GET /products/12  →  404  {"error": "Product not found"}

Your task: write a wrapper runMany(sql, rows) that counts one query, prepares the statement once and runs it for every row. Then add POST /products/batch: turn the JSON list into [name, price] arrays, open a transaction with BEGIN, insert them all with one runMany call, roll back and answer 400 if a row is refused, and otherwise commit once, delete the cached list and answer {"added": …} with 201.


Assignment
Follow the Coding Tutorial to add runMany and the POST /products/batch route.


Hint
Read each value with item.name and item.price: a missing price is undefined, and .run refuses it for you. Put db.exec("COMMIT") after the try/catch, so it runs only when every row went in.


STEP 1 OF 11
    Ctrl + Enter to check