Writing data
Insert, update, upsert, and delete, with the PostgREST status rules.
Writes use POST, PATCH, PUT, and DELETE. The request body is JSON, the
filters that scope an update or delete are the same query-string filters reads
use, and the Prefer header controls what comes back.
Insert
POST a JSON object, or an array of objects, to the table:
curl -X POST 'localhost:3000/directors' \
-H 'Content-Type: application/json' \
-d '{ "id": 4, "name": "Celine Sciamma" }'
A successful insert is 201 Created. For a single inserted row, the response
carries a Location header with the primary-key filter that selects it.
Insert several rows at once by posting an array:
curl -X POST 'localhost:3000/films' \
-H 'Content-Type: application/json' \
-d '[
{ "id": 6, "title": "Portrait of a Lady on Fire", "year": 2019, "rating": 8.1, "director_id": 4 }
]'
Get the written rows back
By default a write returns no body. Ask for the affected rows with
Prefer: return=representation:
curl -X POST 'localhost:3000/directors' \
-H 'Content-Type: application/json' \
-H 'Prefer: return=representation' \
-d '{ "id": 5, "name": "Chloe Zhao" }'
# 201 Created
# [ { "id": 5, "name": "Chloe Zhao" } ]
return=minimal is the default and returns 204 No Content.
Update
PATCH updates the rows a filter selects. The body holds the columns to change:
# bump the rating of film 1
curl -X PATCH 'localhost:3000/films?id=eq.1' \
-H 'Content-Type: application/json' \
-d '{ "rating": 8.6 }'
A PATCH with no filter updates every row, so scope it carefully. As with
insert, Prefer: return=representation returns the updated rows, and the status
is 200 with a representation or 204 without.
Upsert
A POST with Prefer: resolution=merge-duplicates inserts new rows and updates
existing ones on a primary-key or unique conflict:
curl -X POST 'localhost:3000/directors' \
-H 'Content-Type: application/json' \
-H 'Prefer: resolution=merge-duplicates' \
-d '{ "id": 1, "name": "Bong Joon-ho (updated)" }'
resolution=ignore-duplicates keeps the existing row instead. PUT upserts a
single row addressed by its full primary key in the filter.
How an upsert lowers differs by backend: PostgreSQL uses ON CONFLICT, MySQL
uses a no-conflict-target form, and SQL Server drives a multi-statement upsert.
The observable result is the same. The ability to target a named unique
constraint is a backend capability, so an upsert that needs one on a backend
that cannot is reported rather than guessed.
Delete
DELETE removes the rows a filter selects:
curl -X DELETE 'localhost:3000/films?id=eq.6'
A delete is 204 No Content, or 200 with Prefer: return=representation to
get the removed rows back. A DELETE with no filter removes every row, so the
same caution as PATCH applies.
Constraint failures
A constraint violation maps to the PostgREST SQLSTATE and the matching HTTP
status. A unique violation is a clean 409 Conflict:
curl -i -X POST 'localhost:3000/directors' \
-H 'Content-Type: application/json' \
-d '{ "id": 1, "name": "duplicate id" }'
# HTTP/1.1 409 Conflict
# { "code": "23505", ... }
See errors for the full mapping from SQLSTATE to status.
Transactions
Each write runs in the backend's transaction. The transaction tier is a capability: Full on the relational engines, and on MongoDB it depends on the deployment topology, which is why the test setup runs MongoDB as a replica set. A write that needs a guarantee the backend cannot provide is reported rather than silently weakened.