Files
postgrest/api.rst
T

368 lines
13 KiB
ReStructuredText

.. role:: sql(code)
:language: sql
Tables and Views
================
All views and tables in the active schema and accessible by the active database role for a request are available for querying. They are exposed in one-level deep routes. For instance the full contents of a table `people` is returned at
.. code-block:: http
GET /people HTTP/1.1
There are no deeply/nested/routes. Each route provides OPTIONS, GET, POST, PATCH, and DELETE verbs depending entirely on database permissions.
.. note::
Why not provide nested routes? Many APIs allow nesting to retrieve related information, such as :code:`/films/1/director`. We offer a more flexible mechanism (inspired by GraphQL) to embed related information. It can handle one-to-many and many-to-many relationships. This is covered in the section about `Resource Embedding`_.
Horizontal Filtering (Rows)
---------------------------
You can filter result rows by adding conditions on columns, each condition a query string parameter. For instance, to return people aged under 13 years old:
.. code-block:: http
GET /people?age=lt.13 HTTP/1.1
Adding multiple parameters conjoins the conditions:
.. code-block:: http
GET /people?age=gte.18&student=is.true HTTP/1.1
These operators are available:
============ =============================================
abbreviation meaning
============ =============================================
eq equals
gte greater than or equal
gt greater than
lte less than or equal
lt less than
neq not equal
like LIKE operator (use * in place of %)
ilike ILIKE operator (use * in place of %)
in one of a list of values e.g. :code:`?a=in.1,2,3`
is checking for exact equality (null,true,false)
@@ full-text search using to_tsquery
@> contains e.g. :code:`?tags=@>.{example, new}`
<@ contained in e.g. :code:`?values=<@{1,2,3}`
not negates another operator, see below
============ =============================================
To negate any operator, prefix it with :code:`not` like :code:`?a=not.eq.2`.
For more complicated filters (such as those involving condition 1 OR condition 2) you will have to create a new view in the database.
Vertical Filtering (Columns)
----------------------------
When certain columns are wide (such as those holding binary data), it is more efficient for the server to withold them in a response. The client can specify which columns are required using the :sql:`select` parameter.
.. code-block:: http
GET /people?select=fname,age HTTP/1.1
The default is :sql:`*`, meaning all columns. This value will become more important below in :ref:`Resource Embedding`_.
.. _computed_cols:
Computed Columns
~~~~~~~~~~~~~~~~
Filters may be applied to computed columns as well as actual table/view columns, even though the computed columns will not appear in the output. For example, to search first and last names at once we can create a computed column that will not appear in the output but can be used in a filter:
.. code-block:: postgres
CREATE TABLE people (
fname text,
lname text
);
CREATE FUNCTION full_name(people) RETURNS text AS $$
SELECT $1.fname || ' ' || $1.lname;
$$ LANGUAGE SQL;
-- (optional) add an index to speed up anticipated query
CREATE INDEX people_full_name_idx ON people
USING GIN (to_tsvector('english', fname || ' ' || lname));
A full-text search on the computed column:
.. code-block:: http
GET /people?full_name=@@.Beckett HTTP/1.1
Ordering
--------
The reserved word :sql:`order` reorders the response rows. It uses a comma-separated list of columns and directions:
.. code-block:: http
GET /people?order=age.desc,height.asc HTTP/1.1
If no direction is specified it defaults to ascending order:
.. code-block:: http
GET /people?order=age HTTP/1.1
If you care where nulls are sorted, add nullsfirst or nullslast:
.. code-block:: http
GET /people?order=age.nullsfirst HTTP/1.1
.. code-block:: http
GET /people?order=age.desc.nullslast HTTP/1.1
You can also use :ref:`computed_cols` to order the results, even though the computed columns will not appear in the output.
Limits and Pagination
---------------------
PostgREST uses HTTP range headers to describe the size of results. Every response contains the current range and, if requested, the total number of results:
.. code-block:: http
HTTP/1.1 200 OK
Range-Unit: items
Content-Range: 0-14/*
Here items zero through fourteen are returned. This information is available in every response and can help you render pagination controls on the client. This is an RFC7233-compliant solution that keeps the response JSON cleaner.
There are two ways to apply a limit and offset rows: through request headers or query params. When using headers you specify the range of rows desired. This request gets the first twenty people.
.. code-block:: http
GET /people HTTP/1.1
Range-Unit: items
Range: 0-19
Note that the server may respond with fewer if unable to meet your request:
.. code-block:: http
HTTP/1.1 200 OK
Range-Unit: items
Content-Range: 0-17/*
You may also request open-ended ranges for an offset with no limit, e.g. :code:`Range: 10-`.
The other way to request a limit or offset is with query parameters. For example
.. code-block:: http
GET /people?limit=15&offset=30 HTTP/1.1
This method is also useful for embedded resources, which we will cover in another section. The server always responds with range headers even if you use query parameters to limit the query.
In order to obtain the total size of the table or view (such as when rendering the last page link in a pagination control), specify your preference in a request header:
.. code-block:: http
GET /bigtable HTTP/1.1
Range-Unit: items
Range: 0-24
Prefer: count=exact
Note that the larger the table the slower this query runs in the database. The server will respond with the selected range and total
.. code-block:: http
HTTP/1.1 206 Partial Content
Range-Unit: items
Content-Range: 0-24/3573458
Response Format
---------------
PostgREST uses proper HTTP content negotiation (`RFC7231 <https://tools.ietf.org/html/rfc7231#section-5.3>`_) to deliver the desired representation of a resource. That is to say the same API endpoint can respond respond in different formats like JSON or CSV depending on the client request.
Use the Accept request header to specify the acceptable format (or formats) for the response:
.. code-block:: http
GET /people HTTP/1.1
Accept: application/json
The current possibilities are
* \*/\*
* text/csv
* application/json
* application/openapi+json
The server will default to JSON for API endpoints and OpenAPI on the root.
Singular or Plural
------------------
By default PostgREST returns all JSON results in an array, even when there is only one item. For example, requesting :code:`/items?id=eq.1` returns
.. code:: json
[
{ "id": 1 }
]
This can be inconvenient for client code. To return the first result as an object unenclosed by an array, Include a Prefer request header
.. code:: http
GET /items?id=eq.1 HTTP/1.1
Prefer: plurality=singular
This returns
.. code:: json
{ "id": 1 }
.. note::
Many APIs distinguish plural and singular resources using a special nested URL convention e.g. `/stories` vs `/stories/1`. Why do we use `/stories?id=eq.1`? The answer is because a singlular resource is (for us) a row determined by a primary key, and primary keys can be compound (meaning defined across more than one column). The more familiar nested urls consider only a degenerate case of simple and overwhelmingly numeric primary keys. These so-called artificial keys are often introduced automatically by Object Relational Mapping libraries.
Admittedly PostgREST could detect when there is an equality condition holding on all columns constituting the primary key and automatically convert to singular. However this could lead to a surprising change of format that breaks unwary client code just by filtering on an extra column. Instead we allow manually specifying singular vs plural to decouple that choice from the URL format.
OpenAPI Support
===============
Every API hosted by PostgREST automatically serves a full `OpenAPI <https://www.openapis.org/>`_ description on the root path. This provides a list of all endpoints, along with supported HTTP verbs and example payloads.
You can use a tool like `Swagger UI <http://swagger.io/swagger-ui/>`_ to create beautiful documentation from the description and to host an interactive web-based dahsboard. The dashboard allows developers to make requests against a live PostgREST server, provides guidance with request headers and example request bodies.
.. note::
The OpenAPI information can go out of date as the schema changes under a running server. To learn how to refresh the cache see :ref:`Schema Reloading`_.
Resource Embedding
==================
In addition to providing RESTful routes for each table and view, PostgREST allows related resources to be included together in a single API call. This reduces the need for multiple API requests. The server uses foreign keys to determine which tables and views can be returned together. For example, consider a database of films and their awards:
.. image:: _static/film.png
As seen above in `vertical_filtering`_ we can request the titles of all films like this:
.. code-block:: http
GET /films?select=title HTTP/1.1
This might return something like
.. code-block:: json
[
{ "title": "Workers Leaving The Lumière Factory In Lyon" },
{ "title": "The Dickson Experimental Sound Film" },
{ "title": "The Haunted Castle" }
]
However because a foreign key constraint exists between Films and Directors, we can request this information be included:
.. code-block:: http
GET /films?select=title,directors{last_name} HTTP/1.1
Which would return
.. code-block:: json
[
{ "title": "Workers Leaving The Lumière Factory In Lyon",
"directors": {
"last_name": "Lumière"
}
},
{ "title": "The Dickson Experimental Sound Film",
"directors": {
"last_name": "Dickson"
}
},
{ "title": "The Haunted Castle",
"directors": {
"last_name": "Méliès"
}
}
]
PostgREST can also detect relations going through join tables. Thus you can request the Actors for Films (which in this case finds the information through Roles). You can also reverse the direction of inclusion, asking for all Directories with each including the list of their Films.
To order the embedded items, you need to specify the tree path in the order parameter. For instance
.. code-block:: http
GET /films?select=*,actors{*}&actors.order=last_name,first_name HTTP/1.1
Note this does not change the order of the Films, but of the list of Actors in each Film.
.. note::
Whenever foreign key relations change in the database schema you must refresh PostgREST's schema cache to allow resource embedding to work properly. See the section :ref:`Schema Reloading`_.
Custom Queries
==============
The PostgREST url grammar limits the kinds of queries clients can perform. It prevents arbitrary, potentially poorly constructed and slow client queries. It's good for quality of service, but means database administrators must create custom views and stored procedures to provide richer endpoints. The most common causes for custom endpoints are
* Table unions and OR-conditions in the where clause
* More complicated joins than those provided by `Resource Embedding`_
* Geospatial queries that require an argument, like "points near (lat,lon)"
* More sophisticated full-text search than a simple use of the :sql:`@@` filter
Stored Procedures
=================
Every stored procedure in the API-exposed database schema is accessible under the :code:`/rpc` prefix. The API endpoint supports only POST which executes the function.
.. code:: http
POST /rpc/function_name HTTP/1.1
Procedures must used `named arguments <https://www.postgresql.org/docs/current/static/sql-syntax-calling-funcs.html#SQL-SYNTAX-CALLING-FUNCS-NAMED>`_. To supply arguments in an API call, include a JSON object in the request payload and each key/value of the object will become an argument.
For instance, assume we have created this function in the database.
.. code:: plpgsql
CREATE FUNCTION add_them(a integer, b integer)
RETURNS integer AS $$
SELECT $1 + $2;
$$ LANGUAGE SQL IMMUTABLE STRICT;
The client can call it by posting an object like
.. code:: http
POST /rpc/add_them HTTP/1.1
{ "a": 1, "b": 2}
The keys of the object match the parameter names. Note that PostgreSQL converts parameter names to lowercase unless you quote them like :sql:`CREATE FUNCTION foo("mixedCase" text) ...`.
.. note::
Why the `/rpc` prefix? One reason is to avoid name collisions between views and procedures. It also helps emphasize to API consumers that these functions are not normal restful things. The functions can have arbitrary and surprising behavior, not the standard "post creates a resource" thing that users expect from the other routes.
We are considering allowing GET requests for functions that are marked non-volatile. Allowing GET is important for HTTP caching. However we still must decide how to pass function parameters since request bodies are not allowed. Also some query string arguments are already reserved for shaping/filtering the output.
Insertions / Updates
====================
Getting Results
---------------
Bulk Insert
-----------
Deletions
=========