├── .github
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ └── ci.yaml
├── .gitignore
├── .readthedocs.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.sh
├── default.nix
├── diagrams
├── README.md
├── boxoffice.er
├── db.tex
├── employees.er
├── film.er
├── orders.er
├── premieres.er
├── presidents.er
└── users.er
├── docs
├── _static
│ ├── 2ndquadrant.png
│ ├── boxoffice.png
│ ├── code-build.webp
│ ├── css
│ │ └── custom.css
│ ├── cybertec-new.png
│ ├── cybertec.png
│ ├── db.png
│ ├── employees.png
│ ├── empty.png
│ ├── favicon.ico
│ ├── film.png
│ ├── gnuhost.png
│ ├── how-tos
│ │ ├── htmx-demo.gif
│ │ ├── htmx-edit-delete.gif
│ │ ├── htmx-insert.gif
│ │ └── htmx-simple.jpg
│ ├── logo.png
│ ├── neon.jpg
│ ├── oblivious.jpg
│ ├── orders.png
│ ├── presidents.png
│ ├── retool.png
│ ├── security-anon-choice.png
│ ├── security-roles.png
│ ├── supabase.png
│ ├── tembo.png
│ ├── timescaledb.png
│ ├── tuts
│ │ ├── tut0-request-flow.png
│ │ └── tut1-jwt-io.png
│ ├── users.png
│ └── win-err-dialog.png
├── conf.py
├── ecosystem.rst
├── explanations
│ ├── db_authz.rst
│ ├── install.rst
│ ├── nginx.rst
│ └── schema_isolation.rst
├── how-tos
│ ├── create-soap-endpoint.rst
│ ├── providing-html-content-using-htmx.rst
│ ├── providing-images-for-img.rst
│ ├── sql-user-management-using-postgres-users-and-passwords.rst
│ ├── sql-user-management.rst
│ └── working-with-postgresql-data-types.rst
├── index.rst
├── integrations
│ ├── greenplum.rst
│ ├── jwt_gen.rst
│ ├── pg-safeupdate.rst
│ └── systemd.rst
├── references
│ ├── api.rst
│ ├── api
│ │ ├── aggregate_functions.rst
│ │ ├── computed_fields.rst
│ │ ├── cors.rst
│ │ ├── domain_representations.rst
│ │ ├── media_type_handlers.rst
│ │ ├── openapi.rst
│ │ ├── options.rst
│ │ ├── pagination_count.rst
│ │ ├── preferences.rst
│ │ ├── resource_embedding.rst
│ │ ├── resource_representation.rst
│ │ ├── schemas.rst
│ │ ├── stored_procedures.rst
│ │ ├── tables_views.rst
│ │ └── url_grammar.rst
│ ├── auth.rst
│ ├── configuration.rst
│ ├── connection_pool.rst
│ ├── errors.rst
│ ├── health_check.rst
│ ├── observability.rst
│ ├── schema_cache.rst
│ └── transactions.rst
├── shared
│ └── installation.rst
└── tutorials
│ ├── tut0.rst
│ └── tut1.rst
├── livereload_docs.py
├── postgrest.dict
├── requirements.txt
└── shell.nix
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: weekly
7 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - v*
8 | pull_request:
9 | branches:
10 | - main
11 | - v*
12 |
13 | jobs:
14 | build:
15 | name: Build docs
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v4
19 | - uses: cachix/install-nix-action@v25
20 | - run: nix-env -f default.nix -iA build
21 | - run: postgrest-docs-build
22 | - run: git diff --exit-code HEAD locales || echo "Please commit changes to the locales/ folder after running postgrest-docs-build."
23 |
24 | spellcheck:
25 | name: Run spellcheck
26 | runs-on: ubuntu-latest
27 | steps:
28 | - uses: actions/checkout@v4
29 | - uses: cachix/install-nix-action@v25
30 | - run: nix-env -f default.nix -iA spellcheck
31 | - run: postgrest-docs-spellcheck
32 |
33 | dictcheck:
34 | name: Run dictcheck
35 | runs-on: ubuntu-latest
36 | steps:
37 | - uses: actions/checkout@v4
38 | - uses: cachix/install-nix-action@v25
39 | - run: nix-env -f default.nix -iA dictcheck
40 | - run: postgrest-docs-dictcheck
41 |
42 | linkcheck:
43 | name: Run linkcheck
44 | if: github.base_ref == 'main'
45 | runs-on: ubuntu-latest
46 | steps:
47 | - uses: actions/checkout@v4
48 | - uses: cachix/install-nix-action@v25
49 | - run: nix-env -f default.nix -iA linkcheck
50 | - run: postgrest-docs-linkcheck
51 |
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 | Pipfile.lock
3 | *.aux
4 | *.log
5 | diagrams/db.pdf
6 | misspellings
7 | unuseddict
8 | .history
9 | *.mo
10 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | sphinx:
3 | configuration: docs/conf.py
4 | python:
5 | install:
6 | - requirements: requirements.txt
7 | build:
8 | os: ubuntu-22.04
9 | tools:
10 | python: "3.11"
11 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This repository follows the same contribution guidelines as the main PostgREST repository contribution guidelines:
2 |
3 | https://github.com/PostgREST/postgrest/blob/main/.github/CONTRIBUTING.md
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Joe Nelson
2 | Copyright (c) 2019 Steve Chavez
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included
13 | in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repo has been moved into the core repo at https://github.com/PostgREST/postgrest/tree/main/docs.
2 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | # sphinx-intl fails if LC_ALL is not set
5 | export LC_ALL=${LC_ALL:-C}
6 |
7 | function build() {
8 | sphinx-build --color -W -a -n docs -b "$@"
9 | }
10 |
11 | if [ $# -eq 0 ]; then
12 | # clean previous build, otherwise some errors might be supressed
13 | rm -rf "_build/html/default"
14 |
15 | if [ -d languages ]; then
16 | # default to updating all existing locales
17 | build gettext _build/gettext
18 | sphinx-intl update -p _build/gettext
19 | fi
20 |
21 | build html "_build/html/default"
22 | else
23 | # clean previous build, otherwise some errors might be supressed
24 | rm -rf "_build/html/$1"
25 |
26 | # update and build specific locale, can be used to create new locale
27 | build gettext _build/gettext
28 | sphinx-intl update -p _build/gettext -l "$1"
29 |
30 | build html "_build/html/$1" -D "language=$1"
31 | fi
32 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | let
2 | # Commit of the Nixpkgs repository that we want to use.
3 | nixpkgsVersion = {
4 | date = "2024-01-06";
5 | rev = "4bbf5a2eb6046c54f7a29a0964c642ebfe912cbc";
6 | tarballHash = "03p45qdcxqxc41mmzmmyzbkff29vv95vv643z0kd3mf1s2nnsy5b";
7 | };
8 |
9 | # Nix files that describe the Nixpkgs repository. We evaluate the expression
10 | # using `import` below.
11 | pkgs = import
12 | (fetchTarball {
13 | url = "https://github.com/nixos/nixpkgs/archive/${nixpkgsVersion.rev}.tar.gz";
14 | sha256 = nixpkgsVersion.tarballHash;
15 | })
16 | { };
17 |
18 | python = pkgs.python3.withPackages (ps: [
19 | ps.sphinx
20 | ps.sphinx_rtd_theme
21 | ps.livereload
22 | ps.sphinx-tabs
23 | ps.sphinx-copybutton
24 | ps.sphinxext-opengraph
25 | # TODO: Remove override once new sphinx-intl version (> 2.1.0) is released and available in nixpkgs
26 | (ps.sphinx-intl.overrideAttrs (drv: { nativeBuildInputs = drv.nativeBuildInputs ++ [ ps.six ]; }))
27 | ]);
28 | in
29 | rec {
30 | inherit pkgs;
31 |
32 | build =
33 | pkgs.writeShellScriptBin "postgrest-docs-build"
34 | ''
35 | set -euo pipefail
36 |
37 | # build.sh needs to find "sphinx-build"
38 | PATH=${python}/bin:$PATH
39 |
40 | ./build.sh "$@"
41 | '';
42 |
43 | serve =
44 | pkgs.writeShellScriptBin "postgrest-docs-serve"
45 | ''
46 | set -euo pipefail
47 |
48 | # livereload_docs.py needs to find "sphinx-build"
49 | PATH=${python}/bin:$PATH
50 |
51 | ./livereload_docs.py "$@"
52 | '';
53 |
54 | spellcheck =
55 | pkgs.writeShellScriptBin "postgrest-docs-spellcheck"
56 | ''
57 | set -euo pipefail
58 |
59 | FILES=$(find docs -type f -iname '*.rst' | tr '\n' ' ')
60 |
61 | cat $FILES \
62 | | grep -v '^\(\.\.\| \)' \
63 | | sed 's/`.*`//g' \
64 | | ${pkgs.aspell}/bin/aspell -d ${pkgs.aspellDicts.en}/lib/aspell/en_US -p ./postgrest.dict list \
65 | | sort -f \
66 | | tee misspellings
67 | test ! -s misspellings
68 | '';
69 |
70 | # dictcheck detects obsolete entries in postgrest.dict, that are not used anymore
71 | dictcheck =
72 | pkgs.writeShellScriptBin "postgrest-docs-dictcheck"
73 | ''
74 | set -euo pipefail
75 |
76 | FILES=$(find docs -type f -iname '*.rst' | tr '\n' ' ')
77 |
78 | cat postgrest.dict \
79 | | tail -n+2 \
80 | | tr '\n' '\0' \
81 | | xargs -0 -n 1 -i \
82 | sh -c "grep \"{}\" $FILES > /dev/null || echo \"{}\"" \
83 | | tee unuseddict
84 | test ! -s unuseddict
85 | '';
86 |
87 | linkcheck =
88 | pkgs.writeShellScriptBin "postgrest-docs-linkcheck"
89 | ''
90 | set -euo pipefail
91 |
92 | ${python}/bin/sphinx-build --color -b linkcheck docs _build
93 | '';
94 |
95 | check =
96 | pkgs.writeShellScriptBin "postgrest-docs-check"
97 | ''
98 | set -euo pipefail
99 | ${build}/bin/postgrest-docs-build
100 | ${dictcheck}/bin/postgrest-docs-dictcheck
101 | ${linkcheck}/bin/postgrest-docs-linkcheck
102 | ${spellcheck}/bin/postgrest-docs-spellcheck
103 | '';
104 | }
105 |
--------------------------------------------------------------------------------
/diagrams/README.md:
--------------------------------------------------------------------------------
1 | ## ERD
2 |
3 | The ER diagrams were created with https://github.com/BurntSushi/erd/.
4 |
5 | You can go download erd from https://github.com/BurntSushi/erd/releases and then do:
6 |
7 | ```bash
8 | ./erd_static-x86-64 -i diagrams/film.er -o docs/_static/film.png
9 | ```
10 |
11 | The fonts used belong to the GNU FreeFont family. You can download them here: http://ftp.gnu.org/gnu/freefont/
12 |
13 | ## LaTeX
14 |
15 | The schema structure diagram is done with LaTeX. You can use a GUI like https://www.mathcha.io/editor to create the .tex file.
16 |
17 | Then use this command to generate the png file.
18 |
19 | ```bash
20 | pdflatex --shell-escape -halt-on-error db.tex
21 |
22 | ## and move it to the static folder(it's not easy to do it in one go with the pdflatex)
23 | mv db.png ../docs/_static/
24 | ```
25 |
26 | LaTeX is used because it's a tweakable plain text format.
27 |
28 | You can install the full latex suite with `nix`:
29 |
30 | ```
31 | nix-env -iA texlive.combined.scheme-full
32 | ```
33 |
34 | To tweak the file with a live reload environment use:
35 |
36 | ```bash
37 | # open the pdf(zathura used as an example)
38 | zathura db.pdf &
39 |
40 | # live reload with entr
41 | echo db.tex | entr pdflatex --shell-escape -halt-on-error db.tex
42 | ```
43 |
--------------------------------------------------------------------------------
/diagrams/boxoffice.er:
--------------------------------------------------------------------------------
1 | entity {font: "FreeSans"}
2 | relationship {font: "FreeMono"}
3 |
4 | [Box_Office]
5 | *bo_date
6 | *+film_id
7 | gross_revenue
8 |
9 | [Films]
10 | *id
11 | +director_id
12 | title
13 | `...`
14 |
15 | Box_Office +--1 Films
16 |
--------------------------------------------------------------------------------
/diagrams/db.tex:
--------------------------------------------------------------------------------
1 | \documentclass[convert]{standalone}
2 | \usepackage{amsmath}
3 | \usepackage{tikz}
4 | \usepackage{mathdots}
5 | \usepackage{yhmath}
6 | \usepackage{cancel}
7 | \usepackage{color}
8 | \usepackage{siunitx}
9 | \usepackage{array}
10 | \usepackage{multirow}
11 | \usepackage{amssymb}
12 | \usepackage{gensymb}
13 | \usepackage{tabularx}
14 | \usepackage{booktabs}
15 | \usetikzlibrary{fadings}
16 | \usetikzlibrary{patterns}
17 | \usetikzlibrary{shadows.blur}
18 | \usetikzlibrary{shapes}
19 |
20 | \begin{document}
21 |
22 | \newcommand\customScale{0.35}
23 |
24 | \begin{tikzpicture}[x=0.75pt,y=0.75pt,yscale=-1,xscale=1, scale=\customScale, every node/.style={scale=\customScale}]
25 |
26 | %Shape: Can [id:dp7234864758664346]
27 | \draw [fill={rgb, 255:red, 47; green, 97; blue, 144 } ,fill opacity=1 ] (497.5,51.5) -- (497.5,255.5) .. controls (497.5,275.66) and (423.18,292) .. (331.5,292) .. controls (239.82,292) and (165.5,275.66) .. (165.5,255.5) -- (165.5,51.5) .. controls (165.5,31.34) and (239.82,15) .. (331.5,15) .. controls (423.18,15) and (497.5,31.34) .. (497.5,51.5) .. controls (497.5,71.66) and (423.18,88) .. (331.5,88) .. controls (239.82,88) and (165.5,71.66) .. (165.5,51.5) ;
28 | %Shape: Rectangle [id:dp7384065579958246]
29 | \draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (189,115) -- (252.5,115) -- (252.5,155) -- (189,155) -- cycle ;
30 | %Shape: Rectangle [id:dp24763906430298177]
31 | \draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (292,118) -- (362,118) -- (362,158) -- (292,158) -- cycle ;
32 | %Shape: Rectangle [id:dp3775601612537265]
33 | \draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (397,114) -- (467,114) -- (467,154) -- (397,154) -- cycle ;
34 | %Shape: Rectangle [id:dp7071457022893852]
35 | \draw [fill={rgb, 255:red, 248; green, 231; blue, 28 } ,fill opacity=1 ] (269,199) -- (397.5,199) -- (397.5,273) -- (269,273) -- cycle ;
36 | %Straight Lines [id:da8846759047437789]
37 | \draw (268,234) -- (226.44,155.77) ;
38 | \draw [shift={(225.5,154)}, rotate = 422.02] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
39 | %Straight Lines [id:da6908444738113828]
40 | \draw (309.5,198) -- (307.6,161) ;
41 | \draw [shift={(307.5,159)}, rotate = 447.06] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
42 | %Straight Lines [id:da7168757864413169]
43 | \draw (398.5,233) -- (431.72,154.84) ;
44 | \draw [shift={(432.5,153)}, rotate = 473.03] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
45 | %Up Down Arrow [id:dp14059754167108496]
46 | \draw [fill={rgb, 255:red, 126; green, 211; blue, 33 } ,fill opacity=1 ] (312.5,288.5) -- (330,273) -- (347.5,288.5) -- (338.75,288.5) -- (338.75,319.5) -- (347.5,319.5) -- (330,335) -- (312.5,319.5) -- (321.25,319.5) -- (321.25,288.5) -- cycle ;
47 |
48 | % Text Node
49 | \draw (201,129) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
50 | % Text Node
51 | \draw (307,130) node [anchor=north west][inner sep=0.75pt] [align=left ] {tables};
52 | % Text Node
53 | \draw (414,127) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
54 | % Text Node
55 | \draw (272,203) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 0; green, 0; blue, 0 } ,opacity=1 ] [align=center] { \\ views \\ + \\ \ \ stored procedures};
56 |
57 | % Text Node
58 | \draw (322,178) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{api}};
59 | % Text Node
60 | \draw (190,97) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{internal}};
61 | % Text Node
62 | \draw (300,99) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{private}};
63 | % Text Node
64 | \draw (417,101) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{core}};
65 | % Text Node
66 | \draw (358,306) node [anchor=north west][inner sep=0.75pt] [align=left] {REST};
67 |
68 | \end{tikzpicture}
69 |
70 |
71 | \end{document}
72 |
--------------------------------------------------------------------------------
/diagrams/employees.er:
--------------------------------------------------------------------------------
1 | # Build using: -e ortho
2 |
3 | entity {font: "FreeSans"}
4 | relationship {font: "FreeMono"}
5 |
6 | [Employees]
7 | *id
8 | first_name
9 | last_name
10 | +supervisor_id
11 |
12 | Employees 1--* Employees
13 |
--------------------------------------------------------------------------------
/diagrams/film.er:
--------------------------------------------------------------------------------
1 | entity {font: "FreeSans"}
2 | relationship {font: "FreeSerif"}
3 |
4 | [Films]
5 | *id
6 | +director_id
7 | title
8 | year
9 | rating
10 | language
11 |
12 | [Directors]
13 | *id
14 | first_name
15 | last_name
16 |
17 | [Actors]
18 | *id
19 | first_name
20 | last_name
21 |
22 | [Roles]
23 | *+film_id
24 | *+actor_id
25 | character
26 |
27 | [Competitions]
28 | *id
29 | name
30 | year
31 |
32 | [Nominations]
33 | *+competition_id
34 | *+film_id
35 | rank
36 |
37 | [Technical_Specs]
38 | *+film_id
39 | runtime
40 | camera
41 | sound
42 |
43 | Roles *--1 Actors
44 | Roles *--1 Films
45 |
46 | Nominations *--1 Competitions
47 | Nominations *--1 Films
48 |
49 | Films *--1 Directors
50 |
51 | Films 1--1 Technical_Specs
52 |
--------------------------------------------------------------------------------
/diagrams/orders.er:
--------------------------------------------------------------------------------
1 | # Build using: -e ortho
2 |
3 | entity {font: "FreeSans"}
4 | relationship {font: "FreeMono"}
5 |
6 | [Addresses]
7 | *id
8 | name
9 | city
10 | state
11 | postal_code
12 |
13 | [Orders]
14 | *id
15 | name
16 | +billing_address_id
17 | +shipping_address_id
18 |
19 | Orders *--1 Addresses
20 | Orders *--1 Addresses
21 |
--------------------------------------------------------------------------------
/diagrams/premieres.er:
--------------------------------------------------------------------------------
1 | entity {font: "FreeSans"}
2 | relationship {font: "FreeMono"}
3 |
4 | [Premieres]
5 | *id
6 | location
7 | date
8 | +film_id
9 |
10 | [Films]
11 | *id
12 | +director_id
13 | title
14 | `...`
15 |
16 | Premieres *--1 Films
17 |
--------------------------------------------------------------------------------
/diagrams/presidents.er:
--------------------------------------------------------------------------------
1 | # Build using: -e ortho
2 |
3 | entity {font: "FreeSans"}
4 | relationship {font: "FreeMono"}
5 |
6 | [Presidents]
7 | *id
8 | first_name
9 | last_name
10 | +predecessor_id
11 |
12 | Presidents 1--? Presidents
13 |
--------------------------------------------------------------------------------
/diagrams/users.er:
--------------------------------------------------------------------------------
1 | # Build using: -e ortho
2 |
3 | entity {font: "FreeSans"}
4 | relationship {font: "FreeMono"}
5 |
6 | [Users]
7 | *id
8 | first_name
9 | last_name
10 | username
11 |
12 | [Subscriptions]
13 | *+subscriber_id
14 | *+subscribed_id
15 | type
16 |
17 | Users 1--* Subscriptions
18 | Subscriptions *--1 Users
19 |
--------------------------------------------------------------------------------
/docs/_static/2ndquadrant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/2ndquadrant.png
--------------------------------------------------------------------------------
/docs/_static/boxoffice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/boxoffice.png
--------------------------------------------------------------------------------
/docs/_static/code-build.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/code-build.webp
--------------------------------------------------------------------------------
/docs/_static/css/custom.css:
--------------------------------------------------------------------------------
1 | .wy-nav-content {
2 | max-width: initial;
3 | }
4 |
5 | #postgrest-documentation > h1 {
6 | display: none;
7 | }
8 |
9 | div.wy-menu.rst-pro {
10 | display: none !important;
11 | }
12 |
13 | div.highlight {
14 | background: #fff !important;
15 | }
16 |
17 | div.line-block {
18 | margin-bottom: 0px !important;
19 | }
20 |
21 | #sponsors {
22 | text-align: center;
23 | }
24 |
25 | #sponsors h2 {
26 | text-align: left;
27 | }
28 |
29 | #sponsors img{
30 | margin: 10px;
31 | }
32 |
33 | #thanks{
34 | text-align: center;
35 | }
36 |
37 | #thanks img{
38 | margin: 10px;
39 | }
40 |
41 | #thanks h2{
42 | text-align: left;
43 | }
44 |
45 | #thanks p{
46 | text-align: left;
47 | }
48 |
49 | #thanks ul{
50 | text-align: left;
51 | }
52 |
53 | .image-container {
54 | max-width: 800px;
55 | display: block;
56 | margin-left: auto;
57 | margin-right: auto;
58 | margin-bottom: 24px;
59 | }
60 |
61 | .wy-table-responsive table td {
62 | white-space: normal !important;
63 | }
64 |
65 | .wy-table-responsive {
66 | overflow: visible !important;
67 | }
68 |
69 | #tutorials span.caption-text {
70 | display: none;
71 | }
72 |
73 | #references span.caption-text {
74 | display: none;
75 | }
76 |
77 | #explanations span.caption-text {
78 | display: none;
79 | }
80 |
81 | #how-tos span.caption-text {
82 | display: none;
83 | }
84 |
85 | #ecosystem span.caption-text {
86 | display: none;
87 | }
88 |
89 | #integrations span.caption-text {
90 | display: none;
91 | }
92 |
93 | #api span.caption-text {
94 | display: none;
95 | }
96 |
--------------------------------------------------------------------------------
/docs/_static/cybertec-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/cybertec-new.png
--------------------------------------------------------------------------------
/docs/_static/cybertec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/cybertec.png
--------------------------------------------------------------------------------
/docs/_static/db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/db.png
--------------------------------------------------------------------------------
/docs/_static/employees.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/employees.png
--------------------------------------------------------------------------------
/docs/_static/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/empty.png
--------------------------------------------------------------------------------
/docs/_static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/favicon.ico
--------------------------------------------------------------------------------
/docs/_static/film.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/film.png
--------------------------------------------------------------------------------
/docs/_static/gnuhost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/gnuhost.png
--------------------------------------------------------------------------------
/docs/_static/how-tos/htmx-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/how-tos/htmx-demo.gif
--------------------------------------------------------------------------------
/docs/_static/how-tos/htmx-edit-delete.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/how-tos/htmx-edit-delete.gif
--------------------------------------------------------------------------------
/docs/_static/how-tos/htmx-insert.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/how-tos/htmx-insert.gif
--------------------------------------------------------------------------------
/docs/_static/how-tos/htmx-simple.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/how-tos/htmx-simple.jpg
--------------------------------------------------------------------------------
/docs/_static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/logo.png
--------------------------------------------------------------------------------
/docs/_static/neon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/neon.jpg
--------------------------------------------------------------------------------
/docs/_static/oblivious.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/oblivious.jpg
--------------------------------------------------------------------------------
/docs/_static/orders.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/orders.png
--------------------------------------------------------------------------------
/docs/_static/presidents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/presidents.png
--------------------------------------------------------------------------------
/docs/_static/retool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/retool.png
--------------------------------------------------------------------------------
/docs/_static/security-anon-choice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/security-anon-choice.png
--------------------------------------------------------------------------------
/docs/_static/security-roles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/security-roles.png
--------------------------------------------------------------------------------
/docs/_static/supabase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/supabase.png
--------------------------------------------------------------------------------
/docs/_static/tembo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/tembo.png
--------------------------------------------------------------------------------
/docs/_static/timescaledb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/timescaledb.png
--------------------------------------------------------------------------------
/docs/_static/tuts/tut0-request-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/tuts/tut0-request-flow.png
--------------------------------------------------------------------------------
/docs/_static/tuts/tut1-jwt-io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/tuts/tut1-jwt-io.png
--------------------------------------------------------------------------------
/docs/_static/users.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/users.png
--------------------------------------------------------------------------------
/docs/_static/win-err-dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PostgREST/postgrest-docs/72904efe2f9998e594116fc7d4076b5e086345b0/docs/_static/win-err-dialog.png
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # PostgREST documentation build configuration file, created by
4 | # sphinx-quickstart on Sun Oct 9 16:53:00 2016.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import sys
16 | import os
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | #sys.path.insert(0, os.path.abspath('.'))
22 |
23 | # -- General configuration ------------------------------------------------
24 |
25 | # If your documentation needs a minimal Sphinx version, state it here.
26 | #needs_sphinx = '1.0'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = [
32 | 'sphinx_tabs.tabs',
33 | 'sphinx_copybutton',
34 | 'sphinxext.opengraph',
35 | ]
36 |
37 | # Add any paths that contain templates here, relative to this directory.
38 | templates_path = ['_templates']
39 |
40 | # The suffix(es) of source filenames.
41 | # You can specify multiple suffix as a list of string:
42 | # source_suffix = ['.rst', '.md']
43 | source_suffix = '.rst'
44 |
45 | # The encoding of source files.
46 | #source_encoding = 'utf-8-sig'
47 |
48 | # The master toctree document.
49 | master_doc = 'index'
50 |
51 | # General information about the project.
52 | project = u'PostgREST'
53 | author = u'Joe Nelson, Steve Chavez'
54 | copyright = u'2017, ' + author
55 |
56 | # The version info for the project you're documenting, acts as replacement for
57 | # |version| and |release|, also used in various other places throughout the
58 | # built documents.
59 | #
60 | # The short X.Y version.
61 | version = u'11.2'
62 | # The full version, including alpha/beta/rc tags.
63 | release = u'11.2.0'
64 |
65 | # The language for content autogenerated by Sphinx. Refer to documentation
66 | # for a list of supported languages.
67 | #
68 | # This is also used if you do content translation via gettext catalogs.
69 | # Usually you set "language" from the command line for these cases.
70 | language = 'en'
71 |
72 | # There are two options for replacing |today|: either, you set today to some
73 | # non-false value, then it is used:
74 | #today = ''
75 | # Else, today_fmt is used as the format for a strftime call.
76 | #today_fmt = '%B %d, %Y'
77 |
78 | # List of patterns, relative to source directory, that match files and
79 | # directories to ignore when looking for source files.
80 | # This patterns also effect to html_static_path and html_extra_path
81 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'shared/*.rst']
82 |
83 | # The reST default role (used for this markup: `text`) to use for all
84 | # documents.
85 | #default_role = None
86 |
87 | # If true, '()' will be appended to :func: etc. cross-reference text.
88 | #add_function_parentheses = True
89 |
90 | # If true, the current module name will be prepended to all description
91 | # unit titles (such as .. function::).
92 | #add_module_names = True
93 |
94 | # If true, sectionauthor and moduleauthor directives will be shown in the
95 | # output. They are ignored by default.
96 | #show_authors = False
97 |
98 | # The name of the Pygments (syntax highlighting) style to use.
99 | pygments_style = 'sphinx'
100 |
101 | # A list of ignored prefixes for module index sorting.
102 | #modindex_common_prefix = []
103 |
104 | # If true, keep warnings as "system message" paragraphs in the built documents.
105 | #keep_warnings = False
106 |
107 | # If true, `todo` and `todoList` produce output, else they produce nothing.
108 | todo_include_todos = False
109 |
110 |
111 | # -- Options for HTML output ----------------------------------------------
112 |
113 | # The theme to use for HTML and HTML Help pages. See the documentation for
114 | # a list of builtin themes.
115 | html_theme = 'sphinx_rtd_theme'
116 |
117 | # Theme options are theme-specific and customize the look and feel of a theme
118 | # further. For a list of options available for each theme, see the
119 | # documentation.
120 | #html_theme_options = {}
121 |
122 | # Add any paths that contain custom themes here, relative to this directory.
123 | #html_theme_path = []
124 |
125 | # The name for this set of Sphinx documents.
126 | # " v documentation" by default.
127 | #html_title = u'PostgREST v0.4.0.0'
128 |
129 | # A shorter title for the navigation bar. Default is the same as html_title.
130 | #html_short_title = None
131 |
132 | # The name of an image file (relative to this directory) to place at the top
133 | # of the sidebar.
134 | #html_logo = None
135 |
136 | # The name of an image file (relative to this directory) to use as a favicon of
137 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
138 | # pixels large.
139 | html_favicon = '_static/favicon.ico'
140 |
141 | # Add any paths that contain custom static files (such as style sheets) here,
142 | # relative to this directory. They are copied after the builtin static files,
143 | # so a file named "default.css" will overwrite the builtin "default.css".
144 | html_static_path = ['_static']
145 |
146 | # Add any extra paths that contain custom files (such as robots.txt or
147 | # .htaccess) here, relative to this directory. These files are copied
148 | # directly to the root of the documentation.
149 | #html_extra_path = []
150 |
151 | # If not None, a 'Last updated on:' timestamp is inserted at every page
152 | # bottom, using the given strftime format.
153 | # The empty string is equivalent to '%b %d, %Y'.
154 | #html_last_updated_fmt = None
155 |
156 | # If true, SmartyPants will be used to convert quotes and dashes to
157 | # typographically correct entities.
158 | #html_use_smartypants = True
159 |
160 | # Custom sidebar templates, maps document names to template names.
161 | #html_sidebars = {}
162 |
163 | # Additional templates that should be rendered to pages, maps page names to
164 | # template names.
165 | #html_additional_pages = {}
166 |
167 | # If false, no module index is generated.
168 | #html_domain_indices = True
169 |
170 | # If false, no index is generated.
171 | #html_use_index = True
172 |
173 | # If true, the index is split into individual pages for each letter.
174 | #html_split_index = False
175 |
176 | # If true, links to the reST sources are added to the pages.
177 | #html_show_sourcelink = True
178 |
179 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
180 | #html_show_sphinx = True
181 |
182 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
183 | #html_show_copyright = True
184 |
185 | # If true, an OpenSearch description file will be output, and all pages will
186 | # contain a tag referring to it. The value of this option must be the
187 | # base URL from which the finished HTML is served.
188 | #html_use_opensearch = ''
189 |
190 | # This is the file name suffix for HTML files (e.g. ".xhtml").
191 | #html_file_suffix = None
192 |
193 | # Language to be used for generating the HTML full-text search index.
194 | # Sphinx supports the following languages:
195 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
196 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
197 | #html_search_language = 'en'
198 |
199 | # A dictionary with options for the search language support, empty by default.
200 | # 'ja' uses this config value.
201 | # 'zh' user can custom change `jieba` dictionary path.
202 | #html_search_options = {'type': 'default'}
203 |
204 | # The name of a javascript file (relative to the configuration directory) that
205 | # implements a search results scorer. If empty, the default will be used.
206 | #html_search_scorer = 'scorer.js'
207 |
208 | # Output file base name for HTML help builder.
209 | htmlhelp_basename = 'PostgRESTdoc'
210 |
211 | # -- Options for LaTeX output ---------------------------------------------
212 |
213 | latex_elements = {
214 | # The paper size ('letterpaper' or 'a4paper').
215 | #'papersize': 'letterpaper',
216 |
217 | # The font size ('10pt', '11pt' or '12pt').
218 | #'pointsize': '10pt',
219 |
220 | # Additional stuff for the LaTeX preamble.
221 | #'preamble': '',
222 |
223 | # Latex figure (float) alignment
224 | #'figure_align': 'htbp',
225 | }
226 |
227 | # Grouping the document tree into LaTeX files. List of tuples
228 | # (source start file, target name, title,
229 | # author, documentclass [howto, manual, or own class]).
230 | latex_documents = [
231 | (master_doc, 'PostgREST.tex', u'PostgREST Documentation',
232 | author, 'manual'),
233 | ]
234 |
235 | # The name of an image file (relative to this directory) to place at the top of
236 | # the title page.
237 | #latex_logo = None
238 |
239 | # For "manual" documents, if this is true, then toplevel headings are parts,
240 | # not chapters.
241 | #latex_use_parts = False
242 |
243 | # If true, show page references after internal links.
244 | #latex_show_pagerefs = False
245 |
246 | # If true, show URL addresses after external links.
247 | #latex_show_urls = False
248 |
249 | # Documents to append as an appendix to all manuals.
250 | #latex_appendices = []
251 |
252 | # If false, no module index is generated.
253 | #latex_domain_indices = True
254 |
255 |
256 | # -- Options for manual page output ---------------------------------------
257 |
258 | # One entry per manual page. List of tuples
259 | # (source start file, name, description, authors, manual section).
260 | man_pages = [
261 | (master_doc, 'postgrest', u'PostgREST Documentation',
262 | [author], 1)
263 | ]
264 |
265 | # If true, show URL addresses after external links.
266 | #man_show_urls = False
267 |
268 |
269 | # -- Options for Texinfo output -------------------------------------------
270 |
271 | # Grouping the document tree into Texinfo files. List of tuples
272 | # (source start file, target name, title, author,
273 | # dir menu entry, description, category)
274 | texinfo_documents = [
275 | (master_doc, 'PostgREST', u'PostgREST Documentation',
276 | author, 'PostgREST', 'REST API for any PostgreSQL database',
277 | 'Web'),
278 | ]
279 |
280 | # Documents to append as an appendix to all manuals.
281 | #texinfo_appendices = []
282 |
283 | # If false, no module index is generated.
284 | #texinfo_domain_indices = True
285 |
286 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
287 | #texinfo_show_urls = 'footnote'
288 |
289 | # If true, do not generate a @detailmenu in the "Top" node's menu.
290 | #texinfo_no_detailmenu = False
291 |
292 | # -- Custom setup ---------------------------------------------------------
293 |
294 | def setup(app):
295 | app.add_css_file('css/custom.css')
296 |
297 | # taken from https://github.com/sphinx-doc/sphinx/blob/82dad44e5bd3776ecb6fd8ded656bc8151d0e63d/sphinx/util/requests.py#L42
298 | user_agent = 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0'
299 |
300 | # TODO: stackoverflow only returns 403 right now. We might need to come back later to check whether that's
301 | # a permanent issue or not.
302 | linkcheck_ignore = [r'https://stackoverflow.com/']
303 |
304 | # sphinx-tabs configuration
305 | sphinx_tabs_disable_tab_closing = True
306 |
307 | # sphinxext-opengraph configuration
308 |
309 | ogp_image = '_images/logo.png'
310 | ogp_use_first_image = True
311 | ogp_enable_meta_description = True
312 | ogp_description_length = 300
313 |
314 | ## RTD sets html_baseurl, ensures we use the correct env for canonical URLs
315 | ## Useful to generate correct meta tags for Open Graph
316 | ## Refs: https://github.com/readthedocs/readthedocs.org/issues/10226, https://github.com/urllib3/urllib3/pull/3064
317 | html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/")
318 |
--------------------------------------------------------------------------------
/docs/ecosystem.rst:
--------------------------------------------------------------------------------
1 | .. _community_tutorials:
2 |
3 | Community Tutorials
4 | -------------------
5 |
6 | * `Building a Contacts List with PostgREST and Vue.js `_ -
7 | In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
8 |
9 | * `PostgREST + Auth0: Create REST API in mintutes, and add social login using Auth0 `_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
10 |
11 | * `PostgREST + PostGIS API tutorial in 5 minutes `_ -
12 | In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`s_procs` interface.
13 |
14 | * `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak `_ -
15 | A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
16 |
17 | * `How PostgreSQL triggers work when called with a PostgREST PATCH HTTP request `_ - A tutorial to see how the old and new values are set or not when doing a PATCH request to PostgREST.
18 |
19 | * `REST Data Service on YugabyteDB / PostgreSQL `_
20 |
21 | * `Build data-driven applications with Workers and PostgreSQL `_ - A tutorial on how to integrate with PostgREST and PostgreSQL using Cloudflare Workers.
22 |
23 | * `A poor man's API `_ - Shows how to integrate PostgREST with Apache APISIX as an alternative to Nginx.
24 |
25 | .. * `Accessing a PostgreSQL database in Godot 4 via PostgREST `_
26 |
27 | .. _templates:
28 |
29 | Templates
30 | ---------
31 |
32 | * `compose-postgrest `_ - docker-compose setup with Nginx and HTML example
33 | * `svelte-postgrest-template `_ - Svelte/SvelteKit, PostgREST, EveryLayout and social auth
34 |
35 | .. _eco_example_apps:
36 |
37 | Example Apps
38 | ------------
39 |
40 | * `delibrium-postgrest `_ - example school API and front-end in Vue.js
41 | * `ETH-transactions-storage `_ - indexer for Ethereum to get transaction list by ETH address
42 | * `general `_ - example auth back-end
43 | * `guild-operators `_ - example queries and functions that the Cardano Community uses for their Guild Operators' Repository
44 | * `PostGUI `_ - React Material UI admin panel
45 | * `prospector `_ - data warehouse and visualization platform
46 |
47 | .. _devops:
48 |
49 | DevOps
50 | ------
51 |
52 | * `cloudgov-demo-postgrest `_ - demo for a federally-compliant REST API on cloud.gov
53 | * `cloudstark/helm-charts `_ - helm chart to deploy PostgREST to a Kubernetes cluster via a Deployment and Service
54 | * `cyril-sabourault/postgrest-cloud-run `_ - expose a PostgreSQL database on Cloud SQL using Cloud Run
55 | * `eyberg/postgrest `_ - run PostgREST as a Nanos unikernel
56 | * `jbkarle/postgrest `_ - helm chart with a demo database for development and test purposes
57 |
58 | .. _eco_external_notification:
59 |
60 | External Notification
61 | ---------------------
62 |
63 | These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows stored procedures to initiate actions outside the database such as sending emails.
64 |
65 | * `pg-notify-webhook `_ - trigger webhooks from PostgreSQL's LISTEN/NOTIFY
66 | * `pgsql-listen-exchange `_ - RabbitMQ
67 | * `postgres-websockets `_ - expose web sockets for PostgreSQL's LISTEN/NOTIFY
68 | * `postgresql2websocket `_ - Websockets
69 |
70 |
71 | .. _eco_extensions:
72 |
73 | Extensions
74 | ----------
75 |
76 | * `aiodata `_ - Python, event-based proxy and caching client.
77 | * `pg-safeupdate `_ - prevent full-table updates or deletes
78 | * `postgrest-node `_ - Run a PostgREST server in Node.js via npm module
79 | * `PostgREST-writeAPI `_ - generate Nginx rewrite rules to fit an OpenAPI spec
80 |
81 | .. _clientside_libraries:
82 |
83 | Client-Side Libraries
84 | ---------------------
85 |
86 | * `postgrest-csharp `_ - C#
87 | * `postgrest-dart `_ - Dart
88 | * `postgrest-ex `_ - Elixir
89 | * `postgrest-go `_ - Go
90 | * `postgrest-js `_ - TypeScript/JavaScript
91 | * `postgrest-kt `_ - Kotlin
92 | * `postgrest-py `_ - Python
93 | * `postgrest-rs `_ - Rust
94 | * `postgrest-swift `_ - Swift
95 | * `redux-postgrest `_ - TypeScript/JS, client integrated with (React) Redux.
96 | * `vue-postgrest `_ - Vue.js
97 |
98 |
--------------------------------------------------------------------------------
/docs/explanations/db_authz.rst:
--------------------------------------------------------------------------------
1 | .. _db_authz:
2 |
3 | Database Authorization
4 | ######################
5 |
6 | Database authorization is the process of granting and verifying database access permissions. PostgreSQL manages permissions using the concept of roles.
7 |
8 | Users and Groups
9 | ================
10 |
11 | A role can be thought of as either a database user, or a group of database users, depending on how the role is set up.
12 |
13 | Roles for Each Web User
14 | -----------------------
15 |
16 | PostgREST can accommodate either viewpoint. If you treat a role as a single user then the :ref:`jwt_impersonation` does most of what you need. When an authenticated user makes a request PostgREST will switch into the database role for that user, which in addition to restricting queries, is available to SQL through the :code:`current_user` variable.
17 |
18 | You can use row-level security to flexibly restrict visibility and access for the current user. Here is an `example `_ from Tomas Vondra, a chat table storing messages sent between users. Users can insert rows into it to send messages to other users, and query it to see messages sent to them by other users.
19 |
20 | .. code-block:: postgres
21 |
22 | CREATE TABLE chat (
23 | message_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
24 | message_time TIMESTAMP NOT NULL DEFAULT now(),
25 | message_from NAME NOT NULL DEFAULT current_user,
26 | message_to NAME NOT NULL,
27 | message_subject VARCHAR(64) NOT NULL,
28 | message_body TEXT
29 | );
30 |
31 | ALTER TABLE chat ENABLE ROW LEVEL SECURITY;
32 |
33 | We want to enforce a policy that ensures a user can see only those messages sent by them or intended for them. Also we want to prevent a user from forging the ``message_from`` column with another person's name.
34 |
35 | PostgreSQL allows us to set this policy with row-level security:
36 |
37 | .. code-block:: postgres
38 |
39 | CREATE POLICY chat_policy ON chat
40 | USING ((message_to = current_user) OR (message_from = current_user))
41 | WITH CHECK (message_from = current_user)
42 |
43 | Anyone accessing the generated API endpoint for the chat table will see exactly the rows they should, without our needing custom imperative server-side coding.
44 |
45 | .. warning::
46 |
47 | Roles are namespaced per-cluster rather than per-database so they may be prone to collision.
48 |
49 | Web Users Sharing Role
50 | ----------------------
51 |
52 | Alternately database roles can represent groups instead of (or in addition to) individual users. You may choose that all signed-in users for a web app share the role ``webuser``. You can distinguish individual users by including extra claims in the JWT such as email.
53 |
54 | .. code:: json
55 |
56 | {
57 | "role": "webuser",
58 | "email": "john@doe.com"
59 | }
60 |
61 | SQL code can access claims through PostgREST :ref:`tx_settings`. For instance to get the email claim, call this function:
62 |
63 | .. code:: sql
64 |
65 | current_setting('request.jwt.claims', true)::json->>'email';
66 |
67 | .. note::
68 |
69 | For PostgreSQL < 14
70 |
71 | .. code:: sql
72 |
73 | current_setting('request.jwt.claim.email', true);
74 |
75 | This allows JWT generation services to include extra information and your database code to react to it. For instance the RLS example could be modified to use this ``current_setting`` rather than ``current_user``. The second ``'true'`` argument tells ``current_setting`` to return NULL if the setting is missing from the current configuration.
76 |
77 | Hybrid User-Group Roles
78 | -----------------------
79 |
80 | You can mix the group and individual role policies. For instance we could still have a webuser role and individual users which inherit from it:
81 |
82 | .. code-block:: postgres
83 |
84 | CREATE ROLE webuser NOLOGIN;
85 | -- grant this role access to certain tables etc
86 |
87 | CREATE ROLE user000 NOLOGIN;
88 | GRANT webuser TO user000;
89 | -- now user000 can do whatever webuser can
90 |
91 | GRANT user000 TO authenticator;
92 | -- allow authenticator to switch into user000 role
93 | -- (the role itself has nologin)
94 |
95 | Schemas
96 | =======
97 |
98 | You must explicitly allow roles to access the exposed schemas in :ref:`db-schemas`.
99 |
100 | .. code-block:: postgres
101 |
102 | GRANT USAGE ON SCHEMA api TO webuser;
103 |
104 | Tables
105 | ======
106 |
107 | To let web users access tables you must grant them privileges for the operations you want them to do.
108 |
109 | .. code-block:: postgres
110 |
111 | GRANT
112 | SELECT
113 | , INSERT
114 | , UPDATE(message_body)
115 | , DELETE
116 | ON chat TO webuser;
117 |
118 | You can also choose on which table columns the operation is valid. In the above example, the web user can only update the ``message_body`` column.
119 |
120 | .. _func_privs:
121 |
122 | Functions
123 | =========
124 |
125 | By default, when a function is created, the privilege to execute it is not restricted by role. The function access is ``PUBLIC`` — executable by all roles (more details at `PostgreSQL Privileges page `_). This is not ideal for an API schema. To disable this behavior, you can run the following SQL statement:
126 |
127 | .. code-block:: postgres
128 |
129 | ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
130 |
131 | This will change the privileges for all functions created in the future in all schemas. Currently there is no way to limit it to a single schema. In our opinion it's a good practice anyway.
132 |
133 | .. note::
134 |
135 | It is however possible to limit the effect of this clause only to functions you define. You can put the above statement at the beginning of the API schema definition, and then at the end reverse it with:
136 |
137 | .. code-block:: postgres
138 |
139 | ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO PUBLIC;
140 |
141 | This will work because the :code:`alter default privileges` statement has effect on function created *after* it is executed. See `PostgreSQL alter default privileges `_ for more details.
142 |
143 | After that, you'll need to grant EXECUTE privileges on functions explicitly:
144 |
145 | .. code-block:: postgres
146 |
147 | GRANT EXECUTE ON FUNCTION login TO anonymous;
148 | GRANT EXECUTE ON FUNCTION signup TO anonymous;
149 |
150 | You can also grant execute on all functions in a schema to a higher privileged role:
151 |
152 | .. code-block:: postgres
153 |
154 | GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO web_user;
155 |
156 | Security definer
157 | ----------------
158 |
159 | A function is executed with the privileges of the user who calls it. This means that the user has to have all permissions to do the operations the procedure performs.
160 | If the function accesses private database objects, your :ref:`API roles ` won't be able to successfully execute the function.
161 |
162 | Another option is to define the function with the :code:`SECURITY DEFINER` option. Then only one permission check will take place, the permission to call the function, and the operations in the function will have the authority of the user who owns the function itself.
163 |
164 | .. code-block:: postgres
165 |
166 | -- login as a user wich has privileges on the private schemas
167 |
168 | -- create a sample function
169 | create or replace function login(email text, pass text) returns jwt_token as $$
170 | begin
171 | -- access to a private schema called 'auth'
172 | select auth.user_role(email, pass) into _role;
173 | -- other operations
174 | -- ...
175 | end;
176 | $$ language plpgsql security definer;
177 |
178 | Note the ``SECURITY DEFINER`` keywords at the end of the function. See `PostgreSQL documentation `_ for more details.
179 |
180 | Views
181 | =====
182 |
183 | Views are invoked with the privileges of the view owner, much like stored procedures with the ``SECURITY DEFINER`` option. When created by a SUPERUSER role, all `row-level security `_ policies will be bypassed.
184 |
185 | If you're on PostgreSQL >= 15, this behavior can be changed by specifying the ``security_invoker`` option.
186 |
187 | .. code-block:: postgres
188 |
189 | CREATE VIEW sample_view WITH (security_invoker = true) AS
190 | SELECT * FROM sample_table;
191 |
192 | On PostgreSQL < 15, you can create a non-SUPERUSER role and make this role the view's owner.
193 |
194 | .. code-block:: postgres
195 |
196 | CREATE ROLE api_views_owner NOSUPERUSER NOBYPASSRLS;
197 | ALTER VIEW sample_view OWNER TO api_views_owner;
198 |
199 |
--------------------------------------------------------------------------------
/docs/explanations/install.rst:
--------------------------------------------------------------------------------
1 | .. _install:
2 |
3 | Installation
4 | ############
5 |
6 | The release page has `pre-compiled binaries for macOS, Windows, Linux and FreeBSD `_ .
7 | The Linux binary is a static executable that can be run on any Linux distribution.
8 |
9 | You can also use your OS package manager.
10 |
11 | .. include:: ../shared/installation.rst
12 |
13 | .. _pg-dependency:
14 |
15 | Supported PostgreSQL versions
16 | =============================
17 |
18 | =============== =================================
19 | **Supported** PostgreSQL >= 9.6
20 | =============== =================================
21 |
22 | PostgREST works with all PostgreSQL versions starting from 9.6.
23 |
24 | Running PostgREST
25 | =================
26 |
27 | If you downloaded PostgREST from the release page, first extract the compressed file to obtain the executable.
28 |
29 | .. code-block:: bash
30 |
31 | # For UNIX platforms
32 | tar Jxf postgrest-[version]-[platform].tar.xz
33 |
34 | # On Windows you should unzip the file
35 |
36 | Now you can run PostgREST with the :code:`--help` flag to see usage instructions:
37 |
38 | .. code-block:: bash
39 |
40 | # Running postgrest binary
41 | ./postgrest --help
42 |
43 | # Running postgrest installed from a package manager
44 | postgrest --help
45 |
46 | # You should see a usage help message
47 |
48 | The PostgREST server reads a configuration file as its only argument:
49 |
50 | .. code:: bash
51 |
52 | postgrest /path/to/postgrest.conf
53 |
54 | # You can also generate a sample config file with
55 | # postgrest -e > postgrest.conf
56 | # You'll need to edit this file and remove the usage parts for postgrest to read it
57 |
58 | For a complete reference of the configuration file, see :ref:`configuration`.
59 |
60 | .. note::
61 |
62 | If you see a dialog box like this on Windows, it may be that the :code:`pg_config` program is not in your system path.
63 |
64 | .. image:: ../_static/win-err-dialog.png
65 |
66 | It usually lives in :code:`C:\Program Files\PostgreSQL\\bin`. See this `article `_ about how to modify the system path.
67 |
68 | To test that the system path is set correctly, run ``pg_config`` from the command line. You should see it output a list of paths.
69 |
70 | Docker
71 | ======
72 |
73 | You can get the `official PostgREST Docker image `_ with:
74 |
75 | .. code-block:: bash
76 |
77 | docker pull postgrest/postgrest
78 |
79 | To configure the container image, use :ref:`env_variables_config`.
80 |
81 | There are two ways to run the PostgREST container: with an existing external database, or through docker-compose.
82 |
83 | Containerized PostgREST with native PostgreSQL
84 | ----------------------------------------------
85 |
86 | The first way to run PostgREST in Docker is to connect it to an existing native database on the host.
87 |
88 | .. code-block:: bash
89 |
90 | # Run the server
91 | docker run --rm --net=host \
92 | -e PGRST_DB_URI="postgres://app_user:password@localhost/postgres" \
93 | postgrest/postgrest
94 |
95 | The database connection string above is just an example. Adjust the role and password as necessary. You may need to edit PostgreSQL's :code:`pg_hba.conf` to grant the user local login access.
96 |
97 | .. note::
98 |
99 | Docker on Mac does not support the :code:`--net=host` flag. Instead you'll need to create an IP address alias to the host. Requests for the IP address from inside the container are unable to resolve and fall back to resolution by the host.
100 |
101 | .. code-block:: bash
102 |
103 | sudo ifconfig lo0 10.0.0.10 alias
104 |
105 | You should then use 10.0.0.10 as the host in your database connection string. Also remember to include the IP address in the :code:`listen_address` within postgresql.conf. For instance:
106 |
107 | .. code-block:: bash
108 |
109 | listen_addresses = 'localhost,10.0.0.10'
110 |
111 | You might also need to add a new IPv4 local connection within pg_hba.conf. For instance:
112 |
113 | .. code-block:: bash
114 |
115 | host all all 10.0.0.10/32 trust
116 |
117 | The docker command will then look like this:
118 |
119 | .. code-block:: bash
120 |
121 | # Run the server
122 | docker run --rm -p 3000:3000 \
123 | -e PGRST_DB_URI="postgres://app_user:password@10.0.0.10/postgres" \
124 | postgrest/postgrest
125 |
126 | .. _pg-in-docker:
127 |
128 | Containerized PostgREST *and* db with docker-compose
129 | ----------------------------------------------------
130 |
131 | To avoid having to install the database at all, you can run both it and the server in containers and link them together with docker-compose. Use this configuration:
132 |
133 | .. code-block:: yaml
134 |
135 | # docker-compose.yml
136 |
137 | version: '3'
138 | services:
139 | server:
140 | image: postgrest/postgrest
141 | ports:
142 | - "3000:3000"
143 | environment:
144 | PGRST_DB_URI: postgres://app_user:password@db:5432/app_db
145 | PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
146 | depends_on:
147 | - db
148 | db:
149 | image: postgres
150 | ports:
151 | - "5432:5432"
152 | environment:
153 | POSTGRES_DB: app_db
154 | POSTGRES_USER: app_user
155 | POSTGRES_PASSWORD: password
156 | # Uncomment this if you want to persist the data.
157 | # volumes:
158 | # - "./pgdata:/var/lib/postgresql/data"
159 |
160 | Go into the directory where you saved this file and run :code:`docker-compose up`. You will see the logs of both the database and PostgREST, and be able to access the latter on port 3000.
161 |
162 | If you want to have a visual overview of your API in your browser you can add swagger-ui to your :code:`docker-compose.yml`:
163 |
164 | .. code-block:: yaml
165 |
166 | swagger:
167 | image: swaggerapi/swagger-ui
168 | ports:
169 | - "8080:8080"
170 | expose:
171 | - "8080"
172 | environment:
173 | API_URL: http://localhost:3000/
174 |
175 | With this you can see the swagger-ui in your browser on port 8080.
176 |
177 | .. _build_source:
178 |
179 | Building from Source
180 | ====================
181 |
182 | When a pre-built binary does not exist for your system you can build the project from source.
183 |
184 | .. note::
185 |
186 | We discourage building and using PostgREST on **Alpine Linux** because of a reported GHC memory leak on that platform.
187 |
188 | You can build PostgREST from source with `Stack `_. It will install any necessary Haskell dependencies on your system.
189 |
190 | * `Install Stack `_ for your platform
191 | * Install Library Dependencies
192 |
193 | ===================== =======================================
194 | Operating System Dependencies
195 | ===================== =======================================
196 | Ubuntu/Debian libpq-dev, libgmp-dev, zlib1g-dev
197 | CentOS/Fedora/Red Hat postgresql-devel, zlib-devel, gmp-devel
198 | BSD postgresql12-client
199 | macOS libpq, gmp
200 | ===================== =======================================
201 |
202 | * Build and install binary
203 |
204 | .. code-block:: bash
205 |
206 | git clone https://github.com/PostgREST/postgrest.git
207 | cd postgrest
208 |
209 | # adjust local-bin-path to taste
210 | stack build --install-ghc --copy-bins --local-bin-path /usr/local/bin
211 |
212 | .. note::
213 |
214 | - If building fails and your system has less than 1GB of memory, try adding a swap file.
215 | - `--install-ghc` flag is only needed for the first build and can be omitted in the subsequent builds.
216 |
217 | * Check that the server is installed: :code:`postgrest --help`.
218 |
--------------------------------------------------------------------------------
/docs/explanations/nginx.rst:
--------------------------------------------------------------------------------
1 | .. _nginx:
2 |
3 | Nginx
4 | =====
5 |
6 | PostgREST is a fast way to construct a RESTful API. Its default behavior is great for scaffolding in development. When it's time to go to production it works great too, as long as you take precautions.
7 | PostgREST is a small sharp tool that focuses on performing the API-to-database mapping. We rely on a reverse proxy like Nginx for additional safeguards.
8 |
9 | The first step is to create an Nginx configuration file that proxies requests to an underlying PostgREST server.
10 |
11 | .. code-block:: nginx
12 |
13 | http {
14 | # ...
15 | # upstream configuration
16 | upstream postgrest {
17 | server localhost:3000;
18 | }
19 | # ...
20 | server {
21 | # ...
22 | # expose to the outside world
23 | location /api/ {
24 | default_type application/json;
25 | proxy_hide_header Content-Location;
26 | add_header Content-Location /api/$upstream_http_content_location;
27 | proxy_set_header Connection "";
28 | proxy_http_version 1.1;
29 | proxy_pass http://postgrest/;
30 | }
31 | # ...
32 | }
33 | }
34 |
35 | .. note::
36 |
37 | For ubuntu, if you already installed nginx through :code:`apt` you can add this to the config file in
38 | :code:`/etc/nginx/sites-enabled/default`.
39 |
40 | .. _https:
41 |
42 | HTTPS
43 | -----
44 |
45 | PostgREST aims to do one thing well: add an HTTP interface to a PostgreSQL database. To keep the code small and focused we do not implement HTTPS. Use a reverse proxy such as NGINX to add this, `here's how `_.
46 |
47 | Rate Limiting
48 | -------------
49 |
50 | Nginx supports "leaky bucket" rate limiting (see `official docs `_). Using standard Nginx configuration, routes can be grouped into *request zones* for rate limiting. For instance we can define a zone for login attempts:
51 |
52 | .. code-block:: nginx
53 |
54 | limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
55 |
56 | This creates a shared memory zone called "login" to store a log of IP addresses that access the rate limited urls. The space reserved, 10 MB (:code:`10m`) will give us enough space to store a history of 160k requests. We have chosen to allow only allow one request per second (:code:`1r/s`).
57 |
58 | Next we apply the zone to certain routes, like a hypothetical stored procedure called :code:`login`.
59 |
60 | .. code-block:: nginx
61 |
62 | location /rpc/login/ {
63 | # apply rate limiting
64 | limit_req zone=login burst=5;
65 | }
66 |
67 | The burst argument tells Nginx to start dropping requests if more than five queue up from a specific IP.
68 |
69 | Nginx rate limiting is general and indiscriminate. To rate limit each authenticated request individually you will need to add logic in a :ref:`Custom Validation ` function.
70 |
71 | Alternate URL Structure
72 | -----------------------
73 |
74 | As discussed in :ref:`singular_plural`, there are no special URL forms for singular resources in PostgREST, only operators for filtering. Thus there are no URLs like :code:`/people/1`. It would be specified instead as
75 |
76 | .. code-block:: bash
77 |
78 | curl "http://localhost:3000/people?id=eq.1" \
79 | -H "Accept: application/vnd.pgrst.object+json"
80 |
81 | This allows compound primary keys and makes the intent for singular response independent of a URL convention.
82 |
83 | Nginx rewrite rules allow you to simulate the familiar URL convention. The following example adds a rewrite rule for all table endpoints, but you'll want to restrict it to those tables that have a numeric simple primary key named "id."
84 |
85 | .. code-block:: nginx
86 |
87 | # support /endpoint/:id url style
88 | location ~ ^/([a-z_]+)/([0-9]+) {
89 |
90 | # make the response singular
91 | proxy_set_header Accept 'application/vnd.pgrst.object+json';
92 |
93 | # assuming an upstream named "postgrest"
94 | proxy_pass http://postgrest/$1?id=eq.$2;
95 |
96 | }
97 |
98 | .. TODO
99 | .. Administration
100 | .. API Versioning
101 | .. HTTP Caching
102 | .. Upgrading
103 |
--------------------------------------------------------------------------------
/docs/explanations/schema_isolation.rst:
--------------------------------------------------------------------------------
1 | .. note::
2 |
3 | This page is a work in progress.
4 |
5 | .. _schema_isolation:
6 |
7 | Schema Isolation
8 | ================
9 |
10 | A PostgREST instance exposes all the tables, views, and stored procedures of a single `PostgreSQL schema `_ (a namespace of database objects). This means private data or implementation details can go inside different private schemas and be invisible to HTTP clients.
11 |
12 | It is recommended that you don't expose tables on your API schema. Instead expose views and stored procedures which insulate the internal details from the outside world.
13 | This allows you to change the internals of your schema and maintain backwards compatibility. It also keeps your code easier to refactor, and provides a natural way to do API versioning.
14 |
15 | .. image:: ../_static/db.png
16 |
--------------------------------------------------------------------------------
/docs/how-tos/create-soap-endpoint.rst:
--------------------------------------------------------------------------------
1 | .. _create_soap_endpoint:
2 |
3 | Create a SOAP endpoint
4 | ======================
5 |
6 | :author: `fjf2002 `_
7 |
8 | PostgREST supports :ref:`custom_media`. With a bit of work, SOAP endpoints become possible.
9 |
10 | Minimal Example
11 | ---------------
12 |
13 | This example will simply return the request body, inside a tag ``therequestbodywas``.
14 |
15 | Add the following function to your PostgreSQL database:
16 |
17 | .. code-block:: postgres
18 |
19 | create domain "text/xml" as pg_catalog.xml;
20 |
21 | CREATE OR REPLACE FUNCTION my_soap_endpoint(xml) RETURNS "text/xml" AS $$
22 | DECLARE
23 | nsarray CONSTANT text[][] := ARRAY[
24 | ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/']
25 | ];
26 | BEGIN
27 | RETURN xmlelement(
28 | NAME "soapenv:Envelope",
29 | XMLATTRIBUTES('http://schemas.xmlsoap.org/soap/envelope/' AS "xmlns:soapenv"),
30 | xmlelement(NAME "soapenv:Header"),
31 | xmlelement(
32 | NAME "soapenv:Body",
33 | xmlelement(
34 | NAME theRequestBodyWas,
35 | (xpath('/soapenv:Envelope/soapenv:Body', $1, nsarray))[1]
36 | )
37 | )
38 | );
39 | END;
40 | $$ LANGUAGE plpgsql;
41 |
42 | Do not forget to refresh the :ref:`PostgREST schema cache `.
43 |
44 | Use ``curl`` for a first test:
45 |
46 | .. code-block:: bash
47 |
48 | curl http://localhost:3000/rpc/my_soap_endpoint \
49 | --header 'Content-Type: text/xml' \
50 | --header 'Accept: text/xml' \
51 | --data-binary @- <
53 |
54 |
55 |
56 | My SOAP Content
57 |
58 |
59 |
60 | XML
61 |
62 | The output should contain the original request body within the ``therequestbodywas`` entity,
63 | and should roughly look like:
64 |
65 | .. code-block:: xml
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | My SOAP Content
74 |
75 |
76 |
77 |
78 |
79 |
80 | A more elaborate example
81 | ------------------------
82 |
83 | Here we have a SOAP service that converts a fraction to a decimal value,
84 | with pass-through of PostgreSQL errors to the SOAP response.
85 | Please note that in production you probably should not pass through plain database errors
86 | potentially disclosing internals to the client, but instead handle the errors directly.
87 |
88 |
89 | .. code-block:: postgres
90 |
91 | -- helper function
92 | CREATE OR REPLACE FUNCTION _soap_envelope(body xml)
93 | RETURNS xml
94 | LANGUAGE sql
95 | AS $function$
96 | SELECT xmlelement(
97 | NAME "soapenv:Envelope",
98 | XMLATTRIBUTES('http://schemas.xmlsoap.org/soap/envelope/' AS "xmlns:soapenv"),
99 | xmlelement(NAME "soapenv:Header"),
100 | xmlelement(NAME "soapenv:Body", body)
101 | );
102 | $function$;
103 |
104 | -- helper function
105 | CREATE OR REPLACE FUNCTION _soap_exception(
106 | faultcode text,
107 | faultstring text
108 | )
109 | RETURNS xml
110 | LANGUAGE sql
111 | AS $function$
112 | SELECT _soap_envelope(
113 | xmlelement(NAME "soapenv:Fault",
114 | xmlelement(NAME "faultcode", faultcode),
115 | xmlelement(NAME "faultstring", faultstring)
116 | )
117 | );
118 | $function$;
119 |
120 | CREATE OR REPLACE FUNCTION fraction_to_decimal(xml)
121 | RETURNS "text/xml"
122 | LANGUAGE plpgsql
123 | AS $function$
124 | DECLARE
125 | nsarray CONSTANT text[][] := ARRAY[
126 | ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/']
127 | ];
128 | exc_msg text;
129 | exc_detail text;
130 | exc_hint text;
131 | exc_sqlstate text;
132 | BEGIN
133 | -- simulating a statement that results in an exception:
134 | RETURN _soap_envelope(xmlelement(
135 | NAME "decimalValue",
136 | (
137 | (xpath('/soapenv:Envelope/soapenv:Body/fraction/numerator/text()', $1, nsarray))[1]::text::int
138 | /
139 | (xpath('/soapenv:Envelope/soapenv:Body/fraction/denominator/text()', $1, nsarray))[1]::text::int
140 | )::text::xml
141 | ));
142 | EXCEPTION WHEN OTHERS THEN
143 | GET STACKED DIAGNOSTICS
144 | exc_msg := MESSAGE_TEXT,
145 | exc_detail := PG_EXCEPTION_DETAIL,
146 | exc_hint := PG_EXCEPTION_HINT,
147 | exc_sqlstate := RETURNED_SQLSTATE;
148 | RAISE WARNING USING
149 | MESSAGE = exc_msg,
150 | DETAIL = exc_detail,
151 | HINT = exc_hint;
152 | RETURN _soap_exception(faultcode => exc_sqlstate, faultstring => concat(exc_msg, ', DETAIL: ', exc_detail, ', HINT: ', exc_hint));
153 | END
154 | $function$;
155 |
156 | Let's test the ``fraction_to_decimal`` service with illegal values:
157 |
158 | .. code-block:: bash
159 |
160 | curl http://localhost:3000/rpc/fraction_to_decimal \
161 | --header 'Content-Type: text/xml' \
162 | --header 'Accept: text/xml' \
163 | --data-binary @- <
165 |
166 |
167 |
168 | 42
169 | 0
170 |
171 |
172 |
173 | XML
174 |
175 | The output should roughly look like:
176 |
177 | .. code-block:: xml
178 |
179 |
180 |
181 |
182 |
183 | 22012
184 | division by zero, DETAIL: , HINT:
185 |
186 |
187 |
188 |
189 | References
190 | ----------
191 |
192 | For more information concerning PostgREST, cf.
193 |
194 | - :ref:`s_proc_single_unnamed`
195 | - :ref:`custom_media`. See :ref:`any_handler`, if you need to support an ``application/soap+xml`` media type or if you want to respond with XML without sending a media type.
196 | - :ref:`Nginx reverse proxy `
197 |
198 | For SOAP reference, visit
199 |
200 | - the specification at https://www.w3.org/TR/soap/
201 | - shorter more practical advice is available at https://www.w3schools.com/xml/xml_soap.asp
202 |
--------------------------------------------------------------------------------
/docs/how-tos/providing-images-for-img.rst:
--------------------------------------------------------------------------------
1 | .. _providing_img:
2 |
3 | Providing images for ````
4 | ==============================
5 |
6 | :author: `pkel `_
7 |
8 | In this how-to, you will learn how to create an endpoint for providing images to HTML :code:`` tags without client side JavaScript. In fact, the presented technique is suitable for providing not only images, but arbitrary files.
9 |
10 | We will start with a minimal example that highlights the general concept.
11 | Afterwards we present a more detailed solution that fixes a few shortcomings of the first approach.
12 |
13 | .. warning::
14 |
15 | Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database `_.
16 |
17 | Minimal Example
18 | ---------------
19 |
20 | First, we need a public table for storing the files.
21 |
22 | .. code-block:: postgres
23 |
24 | create table files(
25 | id int primary key
26 | , blob bytea
27 | );
28 |
29 | Let's assume this table contains an image of two cute kittens with id 42. We can retrieve this image in binary format from our PostgREST API by using :ref:`custom_media`:
30 |
31 | .. code-block:: postgres
32 |
33 | create domain "application/octet-stream" as bytea;
34 |
35 | create or replace function file(id int) returns "application/octet-stream" as $$
36 | select blob from files where id = file.id;
37 | $$ language sql;
38 |
39 | Now we can request the RPC endpoint :code:`/rpc/file?id=42` with the :code:`Accept: application/octet-stream` header.
40 |
41 |
42 | .. code-block:: bash
43 |
44 | curl "localhost:3000/rpc/file?id=42" -H "Accept: application/octet-stream"
45 |
46 |
47 | Unfortunately, putting the URL into the :code:`src` of an :code:`` tag will not work. That's because browsers do not send the required :code:`Accept: application/octet-stream` header.
48 | Instead, the :code:`Accept: image/webp` header is sent by many web browsers by default.
49 |
50 | Luckily we can change the accepted media type in the function like so:
51 |
52 | .. code-block:: postgres
53 |
54 | create domain "image/webp" as bytea;
55 |
56 | create or replace function file(id int) returns "image/webp" as $$
57 | select blob from files where id = file.id;
58 | $$ language sql;
59 |
60 | Now, the image will be displayed in the HTML page:
61 |
62 | .. code-block:: html
63 |
64 |
65 |
66 | Improved Version
67 | ----------------
68 |
69 | The basic solution has some shortcomings:
70 |
71 | 1. The response :code:`Content-Type` header is set to :code:`image/webp`.
72 | This might be a problem if you want to specify a different format for the file.
73 | 2. Download requests (e.g. Right Click -> Save Image As) to :code:`/files?select=blob&id=eq.42` will propose :code:`files` as filename.
74 | This might confuse users.
75 | 3. Requests to the binary endpoint are not cached.
76 | This will cause unnecessary load on the database.
77 |
78 | The following improved version addresses these problems.
79 | First, in addition to the minimal example, we need to store the media types and names of our files in the database.
80 |
81 | .. code-block:: postgres
82 |
83 | alter table files
84 | add column type text,
85 | add column name text;
86 |
87 | Next, we set modify the function to set the content type and filename.
88 | We use this opportunity to configure some basic, client-side caching.
89 | For production, you probably want to configure additional caches, e.g. on the :ref:`reverse proxy `.
90 |
91 | .. code-block:: postgres
92 |
93 | create domain "*/*" as bytea;
94 |
95 | create function file(id int) returns "*/*" as
96 | $$
97 | declare headers text;
98 | declare blob bytea;
99 | begin
100 | select format(
101 | '[{"Content-Type": "%s"},'
102 | '{"Content-Disposition": "inline; filename=\"%s\""},'
103 | '{"Cache-Control": "max-age=259200"}]'
104 | , files.type, files.name)
105 | from files where files.id = file.id into headers;
106 | perform set_config('response.headers', headers, true);
107 | select files.blob from files where files.id = file.id into blob;
108 | if FOUND -- special var, see https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-DIAGNOSTICS
109 | then return(blob);
110 | else raise sqlstate 'PT404' using
111 | message = 'NOT FOUND',
112 | detail = 'File not found',
113 | hint = format('%s seems to be an invalid file id', file.id);
114 | end if;
115 | end
116 | $$ language plpgsql;
117 |
118 | With this, we can obtain the cat image from :code:`/rpc/file?id=42`. Thus, the resulting HTML will be:
119 |
120 | .. code-block:: html
121 |
122 |
123 |
--------------------------------------------------------------------------------
/docs/how-tos/sql-user-management.rst:
--------------------------------------------------------------------------------
1 | .. _sql_user_management:
2 |
3 | SQL User Management
4 | ===================
5 |
6 | As mentioned on :ref:`jwt_generation`, an external service can provide user management and coordinate with the PostgREST server using JWT. It’s also possible to support logins entirely through SQL. It’s a fair bit of work, so get ready.
7 |
8 | Storing Users and Passwords
9 | ---------------------------
10 |
11 | The following table, functions, and triggers will live in a :code:`basic_auth` schema that you shouldn't expose publicly in the API. The public views and functions will live in a different schema which internally references this internal information.
12 |
13 | First we'll need a table to keep track of our users:
14 |
15 | .. code:: sql
16 |
17 | -- We put things inside the basic_auth schema to hide
18 | -- them from public view. Certain public procs/views will
19 | -- refer to helpers and tables inside.
20 | create schema if not exists basic_auth;
21 |
22 | create table if not exists
23 | basic_auth.users (
24 | email text primary key check ( email ~* '^.+@.+\..+$' ),
25 | pass text not null check (length(pass) < 512),
26 | role name not null check (length(role) < 512)
27 | );
28 |
29 | We would like the role to be a foreign key to actual database roles, however PostgreSQL does not support these constraints against the :code:`pg_roles` table. We'll use a trigger to manually enforce it.
30 |
31 | .. code-block:: plpgsql
32 |
33 | create or replace function
34 | basic_auth.check_role_exists() returns trigger as $$
35 | begin
36 | if not exists (select 1 from pg_roles as r where r.rolname = new.role) then
37 | raise foreign_key_violation using message =
38 | 'unknown database role: ' || new.role;
39 | return null;
40 | end if;
41 | return new;
42 | end
43 | $$ language plpgsql;
44 |
45 | drop trigger if exists ensure_user_role_exists on basic_auth.users;
46 | create constraint trigger ensure_user_role_exists
47 | after insert or update on basic_auth.users
48 | for each row
49 | execute procedure basic_auth.check_role_exists();
50 |
51 | Next we'll use the pgcrypto extension and a trigger to keep passwords safe in the :code:`users` table.
52 |
53 | .. code-block:: plpgsql
54 |
55 | create extension if not exists pgcrypto;
56 |
57 | create or replace function
58 | basic_auth.encrypt_pass() returns trigger as $$
59 | begin
60 | if tg_op = 'INSERT' or new.pass <> old.pass then
61 | new.pass = crypt(new.pass, gen_salt('bf'));
62 | end if;
63 | return new;
64 | end
65 | $$ language plpgsql;
66 |
67 | drop trigger if exists encrypt_pass on basic_auth.users;
68 | create trigger encrypt_pass
69 | before insert or update on basic_auth.users
70 | for each row
71 | execute procedure basic_auth.encrypt_pass();
72 |
73 | With the table in place we can make a helper to check a password against the encrypted column. It returns the database role for a user if the email and password are correct.
74 |
75 | .. code-block:: plpgsql
76 |
77 | create or replace function
78 | basic_auth.user_role(email text, pass text) returns name
79 | language plpgsql
80 | as $$
81 | begin
82 | return (
83 | select role from basic_auth.users
84 | where users.email = user_role.email
85 | and users.pass = crypt(user_role.pass, users.pass)
86 | );
87 | end;
88 | $$;
89 |
90 | .. _public_ui:
91 |
92 | Public User Interface
93 | ---------------------
94 |
95 | In the previous section we created an internal table to store user information. Here we create a login function which takes an email address and password and returns JWT if the credentials match a user in the internal table.
96 |
97 | Permissions
98 | ~~~~~~~~~~~
99 |
100 | Your database roles need access to the schema, tables, views and functions in order to service HTTP requests.
101 | Recall from the :ref:`roles` that PostgREST uses special roles to process requests, namely the authenticator and
102 | anonymous roles. Below is an example of permissions that allow anonymous users to create accounts and attempt to log in.
103 |
104 | .. code-block:: postgres
105 |
106 | create role anon noinherit;
107 | create role authenticator noinherit;
108 | grant anon to authenticator;
109 |
110 | Then, add ``db-anon-role`` to the configuration file to allow anonymous requests.
111 |
112 | .. code:: ini
113 |
114 | db-anon-role = "anon"
115 |
116 | JWT from SQL
117 | ~~~~~~~~~~~~
118 |
119 | You can create JWT tokens in SQL using the `pgjwt extension `_. It's simple and requires only pgcrypto. If you're on an environment like Amazon RDS which doesn't support installing new extensions, you can still manually run the `SQL inside pgjwt `_ (you'll need to replace ``@extschema@`` with another schema or just delete it) which creates the functions you will need.
120 |
121 | Next write a stored procedure that returns the token. The one below returns a token with a hard-coded role, which expires five minutes after it was issued. Note this function has a hard-coded secret as well.
122 |
123 | .. code-block:: postgres
124 |
125 | CREATE TYPE jwt_token AS (
126 | token text
127 | );
128 |
129 | CREATE FUNCTION jwt_test() RETURNS public.jwt_token AS $$
130 | SELECT public.sign(
131 | row_to_json(r), 'reallyreallyreallyreallyverysafe'
132 | ) AS token
133 | FROM (
134 | SELECT
135 | 'my_role'::text as role,
136 | extract(epoch from now())::integer + 300 AS exp
137 | ) r;
138 | $$ LANGUAGE sql;
139 |
140 | PostgREST exposes this function to clients via a POST request to ``/rpc/jwt_test``.
141 |
142 | .. note::
143 |
144 | To avoid hard-coding the secret in stored procedures, save it as a property of the database.
145 |
146 | .. code-block:: postgres
147 |
148 | -- run this once
149 | ALTER DATABASE mydb SET "app.jwt_secret" TO 'reallyreallyreallyreallyverysafe';
150 |
151 | -- then all functions can refer to app.jwt_secret
152 | SELECT sign(
153 | row_to_json(r), current_setting('app.jwt_secret')
154 | ) AS token
155 | FROM ...
156 |
157 | Logins
158 | ~~~~~~
159 |
160 | As described in `JWT from SQL`_, we'll create a JWT inside our login function. Note that you'll need to adjust the secret key which is hard-coded in this example to a secure (at least thirty-two character) secret of your choosing.
161 |
162 | .. code-block:: postgres
163 |
164 | -- add type
165 | CREATE TYPE basic_auth.jwt_token AS (
166 | token text
167 | );
168 |
169 | -- login should be on your exposed schema
170 | create or replace function
171 | login(email text, pass text) returns basic_auth.jwt_token as $$
172 | declare
173 | _role name;
174 | result basic_auth.jwt_token;
175 | begin
176 | -- check email and password
177 | select basic_auth.user_role(email, pass) into _role;
178 | if _role is null then
179 | raise invalid_password using message = 'invalid user or password';
180 | end if;
181 |
182 | select sign(
183 | row_to_json(r), 'reallyreallyreallyreallyverysafe'
184 | ) as token
185 | from (
186 | select _role as role, login.email as email,
187 | extract(epoch from now())::integer + 60*60 as exp
188 | ) r
189 | into result;
190 | return result;
191 | end;
192 | $$ language plpgsql security definer;
193 |
194 | grant execute on function login(text,text) to anon;
195 |
196 | Since the above :code:`login` function is defined as `security definer `_,
197 | the anonymous user :code:`anon` doesn't need permission to read the :code:`basic_auth.users` table. It doesn't even need permission to access the :code:`basic_auth` schema.
198 | :code:`grant execute on function` is included for clarity but it might not be needed, see :ref:`func_privs` for more details.
199 |
200 | An API request to call this function would look like:
201 |
202 | .. code-block:: bash
203 |
204 | curl "http://localhost:3000/rpc/login" \
205 | -X POST -H "Content-Type: application/json" \
206 | -d '{ "email": "foo@bar.com", "pass": "foobar" }'
207 |
208 | The response would look like the snippet below. Try decoding the token at `jwt.io `_. (It was encoded with a secret of :code:`reallyreallyreallyreallyverysafe` as specified in the SQL code above. You'll want to change this secret in your app!)
209 |
210 | .. code:: json
211 |
212 | {
213 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImZvb0BiYXIuY29tIiwicGFzcyI6ImZvb2JhciJ9.37066TTRlh-1hXhnA9oO9Pj6lgL6zFuJU0iCHhuCFno"
214 | }
215 |
216 |
217 | Alternatives
218 | ~~~~~~~~~~~~
219 |
220 | See the how-to :ref:`sql-user-management-using-postgres-users-and-passwords` for a similar way that completely avoids the table :code:`basic_auth.users`.
221 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. title:: PostgREST Documentation
2 |
3 | PostgREST Documentation
4 | =======================
5 |
6 | .. container:: image-container
7 |
8 | .. figure:: _static/logo.png
9 |
10 | .. image:: https://img.shields.io/github/stars/postgrest/postgrest.svg?style=social
11 | :target: https://github.com/PostgREST/postgrest
12 |
13 | .. image:: https://img.shields.io/github/v/release/PostgREST/postgrest.svg
14 | :target: https://github.com/PostgREST/postgrest/releases
15 |
16 | .. image:: https://img.shields.io/docker/pulls/postgrest/postgrest.svg
17 | :target: https://hub.docker.com/r/postgrest/postgrest/
18 |
19 | .. image:: https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg
20 | :target: https://gitter.im/begriffs/postgrest
21 |
22 | .. image:: https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854
23 | :target: https://www.patreon.com/postgrest
24 |
25 | .. image:: https://img.shields.io/badge/Donate-PayPal-green.svg
26 | :target: https://www.paypal.com/paypalme/postgrest
27 |
28 | |
29 |
30 | PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. The structural constraints and permissions in the database determine the API endpoints and operations.
31 |
32 | Sponsors
33 | --------
34 |
35 | .. container:: image-container
36 |
37 | .. image:: _static/cybertec-new.png
38 | :target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
39 | :width: 13em
40 |
41 | .. image:: _static/gnuhost.png
42 | :target: https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest
43 | :width: 13em
44 |
45 | .. image:: _static/neon.jpg
46 | :target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
47 | :width: 13em
48 |
49 | |
50 |
51 | .. image:: _static/code-build.webp
52 | :target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
53 | :width: 13em
54 |
55 | .. image:: _static/supabase.png
56 | :target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
57 | :width: 13em
58 |
59 | .. image:: _static/tembo.png
60 | :target: https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest
61 | :width: 13em
62 |
63 | .. The static/empty.png(created with `convert -size 320x95 xc:#fcfcfc empty.png`) is an ugly workaround
64 | to create space and center the logos. It's not easy to layout with restructuredText.
65 |
66 | .. .. image:: _static/empty.png
67 | :target: #sponsors
68 | :width: 13em
69 |
70 | |
71 |
72 | Database as Single Source of Truth
73 | ----------------------------------
74 |
75 | Using PostgREST is an alternative to manual CRUD programming. Custom API servers suffer problems. Writing business logic often duplicates, ignores or hobbles database structure. Object-relational mapping is a leaky abstraction leading to slow imperative code. The PostgREST philosophy establishes a single declarative source of truth: the data itself.
76 |
77 | Declarative Programming
78 | -----------------------
79 |
80 | It's easier to ask PostgreSQL to join data for you and let its query planner figure out the details than to loop through rows yourself. It's easier to assign permissions to database objects than to add guards in controllers. (This is especially true for cascading permissions in data dependencies.) It's easier to set constraints than to litter code with sanity checks.
81 |
82 | Leak-proof Abstraction
83 | ----------------------
84 |
85 | There is no ORM involved. Creating new views happens in SQL with known performance implications. A database administrator can now create an API from scratch with no custom programming.
86 |
87 | One Thing Well
88 | --------------
89 |
90 | PostgREST has a focused scope. It works well with other tools like Nginx. This forces you to cleanly separate the data-centric CRUD operations from other concerns. Use a collection of sharp tools rather than building a big ball of mud.
91 |
92 | Getting Support
93 | ----------------
94 |
95 | The project has a friendly and growing community. For discussions, use the Github `discussions page `_ or join our `chat room `_. You can also report or search for bugs/features on the Github `issues `_ page.
96 |
97 | Release Notes
98 | -------------
99 |
100 | The release notes are published on `PostgREST's GitHub release page `_.
101 |
102 | Tutorials
103 | ---------
104 |
105 | Are you new to PostgREST? This is the place to start!
106 |
107 | .. toctree::
108 | :glob:
109 | :caption: Tutorials
110 | :maxdepth: 1
111 |
112 | tutorials/*
113 |
114 | Also have a look at :ref:`install` and :ref:`community_tutorials`.
115 |
116 | References
117 | ----------
118 |
119 | Technical references for PostgREST's functionality.
120 |
121 | .. toctree::
122 | :glob:
123 | :caption: References
124 | :name: references
125 | :maxdepth: 1
126 |
127 | references/auth.rst
128 | references/api.rst
129 | references/transactions.rst
130 | references/connection_pool.rst
131 | references/schema_cache.rst
132 | references/errors.rst
133 | references/configuration.rst
134 | references/observability.rst
135 | references/health_check.rst
136 |
137 | Explanations
138 | ------------
139 |
140 | Key concepts in PostgREST.
141 |
142 | .. toctree::
143 | :glob:
144 | :caption: Explanations
145 | :name: explanations
146 | :maxdepth: 1
147 |
148 | explanations/*
149 |
150 | How-tos
151 | -------
152 |
153 | Recipes that'll help you address specific use-cases.
154 |
155 | .. toctree::
156 | :glob:
157 | :caption: How-to guides
158 | :name: how-tos
159 | :maxdepth: 1
160 |
161 | how-tos/sql-user-*
162 | how-tos/working-*
163 | how-tos/*
164 |
165 | .. _intgrs:
166 |
167 | Integrations
168 | ------------
169 |
170 | .. toctree::
171 | :glob:
172 | :caption: Integrations
173 | :name: integrations
174 | :maxdepth: 1
175 |
176 | integrations/*
177 |
178 | Ecosystem
179 | ---------
180 |
181 | PostgREST has a growing ecosystem of examples, libraries, and experiments. Here is a selection.
182 |
183 | .. toctree::
184 | :caption: Ecosystem
185 | :name: ecosystem
186 | :maxdepth: 1
187 |
188 | ecosystem.rst
189 |
190 | In Production
191 | -------------
192 |
193 | Here are some companies that use PostgREST in production.
194 |
195 | * `Catarse `_
196 | * `Datrium `_
197 | * `Drip Depot `_
198 | * `Image-charts `_
199 | * `Moat `_
200 | * `Netwo `_
201 | * `Nimbus `_
202 | - See how Nimbus uses PostgREST in `Paul Copplestone's blog post `_.
203 | * `OpenBooking `_
204 | * `Redsmin `_
205 | * `Sompani `_
206 | * `Supabase `_
207 |
208 | .. Failing links
209 | * `eGull `_
210 | * `MotionDynamic - Fast highly dynamic video generation at scale `_
211 |
212 | Testimonials
213 | ------------
214 |
215 | "It's so fast to develop, it feels like cheating!"
216 |
217 | -- François-Guillaume Ribreau
218 |
219 | "I just have to say that, the CPU/Memory usage compared to our
220 | Node.js/Waterline ORM based API is ridiculous. It's hard to even push
221 | it over 60/70 MB while our current API constantly hits 1GB running on 6
222 | instances (dynos)."
223 |
224 | -- Louis Brauer
225 |
226 | "I really enjoyed the fact that all of a sudden I was writing
227 | microservices in SQL DDL (and v8 JavaScript functions). I dodged so
228 | much boilerplate. The next thing I knew, we pulled out a full rewrite
229 | of a Spring+MySQL legacy app in 6 months. Literally 10x faster, and
230 | code was super concise. The old one took 3 years and a team of 4
231 | people to develop."
232 |
233 | -- Simone Scarduzio
234 |
235 | "I like the fact that PostgREST does one thing, and one thing well.
236 | While PostgREST takes care of bridging the gap between our HTTP server
237 | and PostgreSQL database, we can focus on the development of our API in
238 | a single language: SQL. This puts the database in the center of our
239 | architecture, and pushed us to improve our skills in SQL programming
240 | and database design."
241 |
242 | -- Eric Bréchemier, Data Engineer, eGull SAS
243 |
244 | "PostgREST is performant, stable, and transparent. It allows us to
245 | bootstrap projects really fast, and to focus on our data and application
246 | instead of building out the ORM layer. In our k8s cluster, we run a few
247 | pods per schema we want exposed, and we scale up/down depending on demand.
248 | Couldn't be happier."
249 |
250 | -- Anupam Garg, Datrium, Inc.
251 |
252 | Contributing
253 | ------------
254 |
255 | Please see the `Contributing guidelines `_ in the main PostgREST repository.
256 |
--------------------------------------------------------------------------------
/docs/integrations/greenplum.rst:
--------------------------------------------------------------------------------
1 | Greenplum
2 | #########
3 |
4 | `Greenplum `_ has been reported to work by adding ``LOGIN`` to the :ref:`anonymous and user roles `.
5 |
6 | For more details, see https://github.com/PostgREST/postgrest/issues/2021.
7 |
--------------------------------------------------------------------------------
/docs/integrations/jwt_gen.rst:
--------------------------------------------------------------------------------
1 | .. _external_jwt:
2 |
3 | External JWT Generation
4 | -----------------------
5 |
6 | JWT from Auth0
7 | ~~~~~~~~~~~~~~
8 |
9 | An external service like `Auth0 `_ can do the hard work transforming OAuth from Github, Twitter, Google etc into a JWT suitable for PostgREST. Auth0 can also handle email signup and password reset flows.
10 |
11 | To use Auth0, create `an application `_ for your app and `an API `_ for your PostgREST server. Auth0 supports both HS256 and RS256 scheme for the issued tokens for APIs. For simplicity, you may first try HS256 scheme while creating your API on Auth0. Your application should use your PostgREST API's `API identifier `_ by setting it with the `audience parameter `_ during the authorization request. This will ensure that Auth0 will issue an access token for your PostgREST API. For PostgREST to verify the access token, you will need to set ``jwt-secret`` on PostgREST config file with your API's signing secret.
12 |
13 | .. note::
14 |
15 | Our code requires a database role in the JWT. To add it you need to save the database role in Auth0 `app metadata `_. Then, you will need to write `a rule `_ that will extract the role from the user's app_metadata and set it as a `custom claim `_ in the access token. Note that, you may use Auth0's `core authorization feature `_ for more complex use cases. Metadata solution is mentioned here for simplicity.
16 |
17 | .. code:: javascript
18 |
19 | function (user, context, callback) {
20 |
21 | // Follow the documentations at
22 | // https://postgrest.org/en/latest/configuration.html#db-role-claim-key
23 | // to set a custom role claim on PostgREST
24 | // and use it as custom claim attribute in this rule
25 | const myRoleClaim = 'https://myapp.com/role';
26 |
27 | user.app_metadata = user.app_metadata || {};
28 | context.accessToken[myRoleClaim] = user.app_metadata.role;
29 | callback(null, user, context);
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/docs/integrations/pg-safeupdate.rst:
--------------------------------------------------------------------------------
1 | pg-safeupdate
2 | #############
3 |
4 | .. _block_fulltable:
5 |
6 | Block Full-Table Operations
7 | ---------------------------
8 |
9 | If the :ref:`active role ` can delete table rows then the DELETE verb is allowed for clients. Here's an API request to delete old rows from a hypothetical logs table:
10 |
11 | .. code-block:: bash
12 |
13 | curl "http://localhost:3000/logs?time=lt.1991-08-06" -X DELETE
14 |
15 | Note that it's very easy to delete the **entire table** by omitting the query parameter!
16 |
17 | .. code-block:: bash
18 |
19 | curl "http://localhost:3000/logs" -X DELETE
20 |
21 | This can happen accidentally such as by switching a request from a GET to a DELETE. To protect against accidental operations use the `pg-safeupdate `_ PostgreSQL extension. It raises an error if UPDATE or DELETE are executed without specifying conditions. To install it you can use the `PGXN `_ network:
22 |
23 | .. code-block:: bash
24 |
25 | sudo -E pgxn install safeupdate
26 |
27 | # then add this to postgresql.conf:
28 | # shared_preload_libraries='safeupdate';
29 |
30 | This does not protect against malicious actions, since someone can add a url parameter that does not affect the result set. To prevent this you must turn to database permissions, forbidding the wrong people from deleting rows, and using `row-level security `_ if finer access control is required.
31 |
--------------------------------------------------------------------------------
/docs/integrations/systemd.rst:
--------------------------------------------------------------------------------
1 | systemd
2 | =======
3 |
4 | For Linux distributions that use **systemd** (Ubuntu, Debian, Archlinux) you can create a daemon in the following way.
5 |
6 | First, create postgrest configuration in ``/etc/postgrest/config``
7 |
8 | .. code-block:: ini
9 |
10 | db-uri = "postgres://:@localhost:5432/"
11 | db-schemas = ""
12 | db-anon-role = ""
13 | jwt-secret = ""
14 |
15 | Create a dedicated ``postgrest`` user with:
16 |
17 | .. code-block:: ini
18 |
19 | sudo useradd -M -U -d /nonexistent -s /usr/sbin/nologin postgrest
20 |
21 | Then create the systemd service file in ``/etc/systemd/system/postgrest.service``
22 |
23 | .. code-block:: ini
24 |
25 | [Unit]
26 | Description=REST API for any PostgreSQL database
27 | After=postgresql.service
28 |
29 | [Service]
30 | User=postgrest
31 | Group=postgrest
32 | ExecStart=/bin/postgrest /etc/postgrest/config
33 | ExecReload=/bin/kill -SIGUSR1 $MAINPID
34 |
35 | [Install]
36 | WantedBy=multi-user.target
37 |
38 | After that, you can enable the service at boot time and start it with:
39 |
40 | .. code-block:: bash
41 |
42 | systemctl enable postgrest
43 | systemctl start postgrest
44 |
45 | ## For reloading the service
46 | ## systemctl restart postgrest
47 |
48 | .. _file_descriptors:
49 |
50 | File Descriptors
51 | ----------------
52 |
53 | File descriptors are kernel resources that are used by HTTP connections (among others). File descriptors are limited per process. The kernel default limit is 1024, which is increased in some Linux distributions.
54 | When under heavy traffic, PostgREST can reach this limit and start showing ``No file descriptors available`` errors. To clear these errors, you can increase the process' file descriptor limit.
55 |
56 | .. code-block:: ini
57 |
58 | [Service]
59 | LimitNOFILE=10000
60 |
--------------------------------------------------------------------------------
/docs/references/api.rst:
--------------------------------------------------------------------------------
1 | .. _api:
2 |
3 | API
4 | ###
5 |
6 | PostgREST exposes three database objects of a schema as resources: tables, views and stored procedures.
7 |
8 | .. toctree::
9 | :glob:
10 | :maxdepth: 1
11 |
12 | api/tables_views.rst
13 | api/stored_procedures.rst
14 | api/schemas.rst
15 | api/computed_fields.rst
16 | api/domain_representations.rst
17 | api/pagination_count.rst
18 | api/resource_embedding.rst
19 | api/resource_representation.rst
20 | api/media_type_handlers.rst
21 | api/aggregate_functions.rst
22 | api/openapi.rst
23 | api/preferences.rst
24 | api/*
25 |
26 | .. raw:: html
27 |
28 |
125 |
--------------------------------------------------------------------------------
/docs/references/api/computed_fields.rst:
--------------------------------------------------------------------------------
1 | .. _computed_cols:
2 |
3 | Computed Fields
4 | ###############
5 |
6 | Computed fields are virtual columns that are not stored in a table. PostgreSQL makes it possible to implement them using functions on table types.
7 |
8 | .. code-block:: postgres
9 |
10 | CREATE TABLE people (
11 | first_name text
12 | , last_name text
13 | , job text
14 | );
15 |
16 | -- a computed field that combines data from two columns
17 | CREATE FUNCTION full_name(people)
18 | RETURNS text AS $$
19 | SELECT $1.first_name || ' ' || $1.last_name;
20 | $$ LANGUAGE SQL;
21 |
22 | Horizontal Filtering on Computed Fields
23 | =======================================
24 |
25 | :ref:`h_filter` can be applied to computed fields. For example, we can do a :ref:`fts` on :code:`full_name`:
26 |
27 | .. code-block:: postgres
28 |
29 | -- (optional) you can add an index on the computed field to speed up the query
30 | CREATE INDEX people_full_name_idx ON people
31 | USING GIN (to_tsvector('english', full_name(people)));
32 |
33 | .. code-block:: bash
34 |
35 | curl "http://localhost:3000/people?full_name=fts.Beckett"
36 |
37 | .. code-block:: json
38 |
39 | [
40 | {"first_name": "Samuel", "last_name": "Beckett", "job": "novelist"}
41 | ]
42 |
43 | Vertical Filtering on Computed Fields
44 | =====================================
45 |
46 | Computed fields won't appear on the response by default but you can use :ref:`v_filter` to include them:
47 |
48 | .. code-block:: bash
49 |
50 | curl "http://localhost:3000/people?select=full_name,job"
51 |
52 | .. code-block:: json
53 |
54 | [
55 | {"full_name": "Samuel Beckett", "job": "novelist"}
56 | ]
57 |
58 | Ordering on Computed Fields
59 | ===========================
60 |
61 | :ref:`ordering` on computed fields is also possible:
62 |
63 | .. code-block:: bash
64 |
65 | curl "http://localhost:3000/people?order=full_name.desc"
66 |
67 | .. important::
68 |
69 | Computed columns must be created in the :ref:`exposed schema ` or in a schema in the :ref:`extra search path ` to be used in this way. When placing the computed column in the :ref:`exposed schema ` you can use an **unnamed** parameter, as in the example above, to prevent it from being exposed as an :ref:`RPC ` under ``/rpc``.
70 |
71 | .. note::
72 |
73 | - PostgreSQL 12 introduced `generated columns `_, which can also compute a value based on other columns. However they're stored, not virtual.
74 | - "computed fields" are documented on https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-USAGE (search for "computed fields")
75 | - On previous PostgREST versions this feature was documented with the name of "computed columns".
76 |
--------------------------------------------------------------------------------
/docs/references/api/cors.rst:
--------------------------------------------------------------------------------
1 | .. _cors:
2 |
3 | CORS
4 | ####
5 |
6 | By default, PostgREST sets highly permissive cross origin resource sharing, that is why it accepts Ajax requests from any domain. This behavior can be configured by using :ref:`server_cors_allowed_origins`.
7 |
8 |
9 | It also handles `preflight requests `_ done by the browser, which are cached using the returned ``Access-Control-Max-Age: 86400`` header (86400 seconds = 24 hours). This is useful to reduce the latency of the subsequent requests.
10 |
11 | A ``POST`` preflight request would look like this:
12 |
13 | .. code-block:: bash
14 |
15 | curl -i "http://localhost:3000/items" \
16 | -X OPTIONS \
17 | -H "Origin: http://example.com" \
18 | -H "Access-Control-Request-Method: POST" \
19 | -H "Access-Control-Request-Headers: Content-Type"
20 |
21 | .. code-block:: http
22 |
23 | HTTP/1.1 200 OK
24 | Access-Control-Allow-Origin: http://example.com
25 | Access-Control-Allow-Credentials: true
26 | Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD
27 | Access-Control-Allow-Headers: Authorization, Content-Type, Accept, Accept-Language, Content-Language
28 | Access-Control-Max-Age: 86400
29 |
30 | .. _allowed_origins:
31 |
32 | Allowed Origins
33 | ===============
34 |
35 | With the following config setting, PostgREST will accept CORS requests from domains :code:`http://example.com` and :code:`http://example2.com`.
36 |
37 |
38 | .. code-block::
39 |
40 | server-cors-allowed-origins="http://example.com, http://example2.com"
41 |
--------------------------------------------------------------------------------
/docs/references/api/domain_representations.rst:
--------------------------------------------------------------------------------
1 | .. _domain_reps:
2 |
3 | Domain Representations
4 | ######################
5 |
6 | Domain Representations separates "how the data is presented" from "how the data is stored". It works by creating `domains `_ and `casts `_, the latter act on the former to present and receive the data in different formats.
7 |
8 | .. contents::
9 | :depth: 1
10 | :local:
11 | :backlinks: none
12 |
13 | Custom Domain
14 | =============
15 |
16 | Suppose you want to use a ``uuid`` type for a primary key and want to present it shortened to web users.
17 |
18 | For this, let's create a domain based on ``uuid``.
19 |
20 | .. code-block:: postgres
21 |
22 | create domain app_uuid as uuid;
23 |
24 | -- and use it as our table PK.
25 | create table profiles(
26 | id app_uuid
27 | , name text
28 | );
29 |
30 | -- some data for the example
31 | insert into profiles values ('846c4ffd-92ce-4de7-8d11-8e29929f4ec4', 'John Doe');
32 |
33 | Domain Response Format
34 | ======================
35 |
36 | We can shorten the ``uuid`` with ``base64`` encoding. Let's use JSON as our response format for this example.
37 |
38 | To change the domain format for JSON, create a function that converts ``app_uuid`` to ``json``.
39 |
40 | .. code-block:: postgres
41 |
42 | -- the name of the function is arbitrary
43 | CREATE OR REPLACE FUNCTION json(app_uuid) RETURNS json AS $$
44 | select to_json(encode(uuid_send($1),'base64'));
45 | $$ LANGUAGE SQL IMMUTABLE;
46 |
47 | -- check it works
48 | select json('846c4ffd-92ce-4de7-8d11-8e29929f4ec4'::app_uuid);
49 | json
50 | ----------------------------
51 | "hGxP/ZLOTeeNEY4pkp9OxA=="
52 |
53 | Then create a CAST to tell PostgREST to convert it automatically whenever a JSON response is requested.
54 |
55 | .. code-block:: postgres
56 |
57 | CREATE CAST (app_uuid AS json) WITH FUNCTION json(app_uuid) AS IMPLICIT;
58 |
59 | With this you can obtain the data in the shortened format.
60 |
61 | .. code-block:: bash
62 |
63 | curl "http://localhost:3000/profiles" \
64 | -H "Accept: application/json"
65 |
66 | .. code-block:: json
67 |
68 | [{"id":"hGxP/ZLOTeeNEY4pkp9OxA==","name":"John Doe"}]
69 |
70 | .. note::
71 |
72 | - Casts on domains are ignored by PostgreSQL, their interpretation is left to the application. We're discussing the possibility of including the Domain Representations behavior on `pgsql-hackers `_.
73 | - It would make more sense to use ``base58`` encoding as it's URL friendly but for simplicity we use ``base64`` (supported natively in PostgreSQL).
74 |
75 | .. important::
76 |
77 | After creating a cast over a domain, you must refresh PostgREST schema cache. See :ref:`schema_reloading`.
78 |
79 | Domain Filter Format
80 | ====================
81 |
82 | For :ref:`h_filter` to work with the shortened format, you need a different conversion.
83 |
84 | PostgREST considers the URL query string to be, in the most generic sense, ``text``. So let's create a function that converts ``text`` to ``app_uuid``.
85 |
86 | .. code-block:: postgres
87 |
88 | -- the name of the function is arbitrary
89 | CREATE OR REPLACE FUNCTION app_uuid(text) RETURNS app_uuid AS $$
90 | select substring(decode($1,'base64')::text from 3)::uuid;
91 | $$ LANGUAGE SQL IMMUTABLE;
92 |
93 | -- plus a CAST to tell PostgREST to use this function
94 | CREATE CAST (text AS app_uuid) WITH FUNCTION app_uuid(text) AS IMPLICIT;
95 |
96 | Now you can filter as usual.
97 |
98 | .. code-block:: bash
99 |
100 | curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \
101 | -H "Accept: application/json"
102 |
103 | .. code-block:: json
104 |
105 | [{"id":"hGxP/ZLOTeeNEY4pkp9OxA==","name":"John Doe"}]
106 |
107 | .. note::
108 |
109 | If there's no CAST from ``text`` to ``app_uuid`` defined, the filter will still work with the native uuid format (``846c4ffd-92ce-4de7-8d11-8e29929f4ec4``).
110 |
111 | Domain Request Body Format
112 | ==========================
113 |
114 | To accept the shortened format in a JSON request body, for example when creating a new record, define a ``json`` to ``app_uuid`` conversion.
115 |
116 | .. code-block:: postgres
117 |
118 | -- the name of the function is arbitrary
119 | CREATE OR REPLACE FUNCTION app_uuid(json) RETURNS public.app_uuid AS $$
120 | -- here we reuse the previous app_uuid(text) function
121 | select app_uuid($1 #>> '{}');
122 | $$ LANGUAGE SQL IMMUTABLE;
123 |
124 | CREATE CAST (json AS public.app_uuid) WITH FUNCTION app_uuid(json) AS IMPLICIT;
125 |
126 | Now we can :ref:`insert` (or :ref:`update`) as usual.
127 |
128 | .. code-block:: bash
129 |
130 | curl "http://localhost:3000/profiles" \
131 | -H "Prefer: return=representation" \
132 | -H "Content-Type: application/json" \
133 | -d @- <`_ also allow us to change the format of the underlying type. However they come with drawbacks that increase complexity.
165 |
166 | 1) Formatting the column in the view makes it `non-updatable `_ since Postgres doesn't know how to reverse the transform. This can be worked around using INSTEAD OF triggers.
167 | 2) When filtering by this column, we get full table scans for the same reason (also applies to :ref:`computed_cols`) . The performance loss here can be avoided with a computed index, or using a materialized generated column.
168 | 3) If the formatted column is used as a foreign key, PostgREST can no longer detect that relationship and :ref:`resource_embedding` breaks. This can be worked around with :ref:`computed_relationships`.
169 |
170 | Domain Representations avoid all the above drawbacks. Their only drawback is that for existing tables, you have to change the column types. But this should be a fast operation since domains are binary coercible with their underlying types. A table rewrite won't be required.
171 |
172 | .. note::
173 |
174 | Why not create a `base type `_ instead? ``CREATE TYPE app_uuid (INTERNALLENGTH = 22, INPUT = app_uuid_parser, OUTPUT = app_uuid_formatter)``.
175 |
176 | Creating base types need superuser, which is restricted on cloud hosted databases. Additionally this way lets “how the data is presented” dictate “how the data is stored” which would be backwards.
177 |
--------------------------------------------------------------------------------
/docs/references/api/openapi.rst:
--------------------------------------------------------------------------------
1 | .. _open-api:
2 |
3 | OpenAPI
4 | =======
5 |
6 | PostgREST automatically serves a full `OpenAPI `_ description on the root path. This provides a list of all endpoints (tables, foreign tables, views, functions), along with supported HTTP verbs and example payloads.
7 |
8 | .. note::
9 |
10 | By default, this output depends on the permissions of the role that is contained in the JWT role claim (or the :ref:`db-anon-role` if no JWT is sent). If you need to show all the endpoints disregarding the role's permissions, set the :ref:`openapi-mode` config to :code:`ignore-privileges`.
11 |
12 | For extra customization, the OpenAPI output contains a "description" field for every `SQL comment `_ on any database object. For instance,
13 |
14 | .. code-block:: sql
15 |
16 | COMMENT ON SCHEMA mammals IS
17 | 'A warm-blooded vertebrate animal of a class that is distinguished by the secretion of milk by females for the nourishment of the young';
18 |
19 | COMMENT ON TABLE monotremes IS
20 | 'Freakish mammals lay the best eggs for breakfast';
21 |
22 | COMMENT ON COLUMN monotremes.has_venomous_claw IS
23 | 'Sometimes breakfast is not worth it';
24 |
25 | These unsavory comments will appear in the generated JSON as the fields, ``info.description``, ``definitions.monotremes.description`` and ``definitions.monotremes.properties.has_venomous_claw.description``.
26 |
27 | Also if you wish to generate a ``summary`` field you can do it by having a multiple line comment, the ``summary`` will be the first line and the ``description`` the lines that follow it:
28 |
29 | .. code-block:: plpgsql
30 |
31 | COMMENT ON TABLE entities IS
32 | $$Entities summary
33 |
34 | Entities description that
35 | spans
36 | multiple lines$$;
37 |
38 | Similarly, you can override the API title by commenting the schema.
39 |
40 | .. code-block:: plpgsql
41 |
42 | COMMENT ON SCHEMA api IS
43 | $$FooBar API
44 |
45 | A RESTful API that serves FooBar data.$$;
46 |
47 | If you need to include the ``security`` and ``securityDefinitions`` options, set the :ref:`openapi-security-active` configuration to ``true``.
48 |
49 | You can use a tool like `Swagger UI `_ to create beautiful documentation from the description and to host an interactive web-based dashboard. The dashboard allows developers to make requests against a live PostgREST server, and provides guidance with request headers and example request bodies.
50 |
51 | .. important::
52 |
53 | The OpenAPI information can go out of date as the schema changes under a running server. See :ref:`schema_reloading`.
54 |
55 | .. _override_openapi:
56 |
57 | Overriding Full OpenAPI Response
58 | --------------------------------
59 |
60 | You can override the whole default response with a function result. To do this, set the function on :ref:`db-root-spec`.
61 |
62 | .. code:: bash
63 |
64 | db-root-spec = "root"
65 |
66 | .. code:: postgres
67 |
68 | create or replace function root() returns json as $_$
69 | declare
70 | openapi json = $$
71 | {
72 | "swagger": "2.0",
73 | "info":{
74 | "title":"Overridden",
75 | "description":"This is a my own API"
76 | }
77 | }
78 | $$;
79 | begin
80 | return openapi;
81 | end
82 | $_$ language plpgsql;
83 |
84 | .. code-block:: bash
85 |
86 | curl http://localhost:3000
87 |
88 | .. code-block:: http
89 |
90 | HTTP/1.1 200 OK
91 |
92 | {
93 | "swagger": "2.0",
94 | "info":{
95 | "title":"Overridden",
96 | "description":"This is a my own API"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/docs/references/api/options.rst:
--------------------------------------------------------------------------------
1 | .. _options_requests:
2 |
3 | OPTIONS method
4 | ==============
5 |
6 | You can verify which HTTP methods are allowed on endpoints for tables and views by using an OPTIONS request. These methods are allowed depending on what operations *can* be done on the table or view, not on the database permissions assigned to them.
7 |
8 | For a table named ``people``, OPTIONS would show:
9 |
10 | .. code-block:: bash
11 |
12 | curl "http://localhost:3000/people" -X OPTIONS -i
13 |
14 | .. code-block:: http
15 |
16 | HTTP/1.1 200 OK
17 | Allow: OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE
18 |
19 | For a view, the methods are determined by the presence of INSTEAD OF TRIGGERS.
20 |
21 | .. table::
22 | :widths: auto
23 |
24 | +--------------------+-------------------------------------------------------------------------------------------------+
25 | | Method allowed | View's requirements |
26 | +====================+=================================================================================================+
27 | | OPTIONS, GET, HEAD | None (Always allowed) |
28 | +--------------------+-------------------------------------------------------------------------------------------------+
29 | | POST | INSTEAD OF INSERT TRIGGER |
30 | +--------------------+-------------------------------------------------------------------------------------------------+
31 | | PUT | INSTEAD OF INSERT TRIGGER, INSTEAD OF UPDATE TRIGGER, also requires the presence of a |
32 | | | primary key |
33 | +--------------------+-------------------------------------------------------------------------------------------------+
34 | | PATCH | INSTEAD OF UPDATE TRIGGER |
35 | +--------------------+-------------------------------------------------------------------------------------------------+
36 | | DELETE | INSTEAD OF DELETE TRIGGER |
37 | +--------------------+-------------------------------------------------------------------------------------------------+
38 | | All the above methods are allowed for |
39 | | `auto-updatable views `_ |
40 | +--------------------+-------------------------------------------------------------------------------------------------+
41 |
42 | For functions, the methods depend on their volatility. ``VOLATILE`` functions allow only ``OPTIONS,POST``, whereas the rest also permit ``GET,HEAD``.
43 |
44 | .. important::
45 |
46 | Whenever you add or remove tables or views, or modify a view's INSTEAD OF TRIGGERS on the database, you must refresh PostgREST's schema cache for OPTIONS requests to work properly. See the section :ref:`schema_reloading`.
47 |
--------------------------------------------------------------------------------
/docs/references/api/pagination_count.rst:
--------------------------------------------------------------------------------
1 | Pagination and Count
2 | ####################
3 |
4 | Pagination controls the number of rows returned for an :doc:`API resource <../api>` response. Combined with the count, you can traverse all the rows of a response.
5 |
6 | .. _limits:
7 |
8 | Limits and Pagination
9 | ---------------------
10 |
11 | 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:
12 |
13 | .. code-block:: http
14 |
15 | HTTP/1.1 200 OK
16 | Range-Unit: items
17 | Content-Range: 0-14/*
18 |
19 | 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.
20 |
21 | Query Parameters
22 | ~~~~~~~~~~~~~~~~
23 |
24 | One way to request limits and offsets is by using query parameters. For example:
25 |
26 | .. code-block:: bash
27 |
28 | curl "http://localhost:3000/people?limit=15&offset=30"
29 |
30 | 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.
31 |
32 | Range Header
33 | ~~~~~~~~~~~~
34 |
35 | You can use headers to specify the range of rows desired.
36 | This request gets the first twenty people:
37 |
38 | .. code-block:: bash
39 |
40 | curl "http://localhost:3000/people" -i \
41 | -H "Range-Unit: items" \
42 | -H "Range: 0-19"
43 |
44 | Note that the server may respond with fewer if unable to meet your request:
45 |
46 | .. code-block:: http
47 |
48 | HTTP/1.1 200 OK
49 | Range-Unit: items
50 | Content-Range: 0-17/*
51 |
52 | You may also request open-ended ranges for an offset with no limit, e.g. :code:`Range: 10-`.
53 |
54 | .. _prefer_count:
55 |
56 | Counting
57 | --------
58 |
59 | In order to obtain the total size of the table (such as when rendering the last page link in a pagination control), you can specify a ``Prefer: count=`` header. The values can be ``exact``, ``planned`` and ``estimated``.
60 |
61 | This also works on views and :ref:`table_functions`.
62 |
63 |
64 | .. _exact_count:
65 |
66 | Exact Count
67 | ~~~~~~~~~~~
68 |
69 | To get the exact count, use ``Prefer: count=exact``.
70 |
71 | .. code-block:: bash
72 |
73 | curl "http://localhost:3000/bigtable" -I \
74 | -H "Range-Unit: items" \
75 | -H "Range: 0-24" \
76 | -H "Prefer: count=exact"
77 |
78 | Note that the larger the table the slower this query runs in the database. The server will respond with the selected range and total
79 |
80 | .. code-block:: http
81 |
82 | HTTP/1.1 206 Partial Content
83 | Range-Unit: items
84 | Content-Range: 0-24/3573458
85 |
86 | .. _planned_count:
87 |
88 | Planned Count
89 | ~~~~~~~~~~~~~
90 |
91 | To avoid the shortcomings of :ref:`exact count `, PostgREST can leverage PostgreSQL statistics and get a fairly accurate and fast count.
92 | To do this, specify the ``Prefer: count=planned`` header.
93 |
94 | .. code-block:: bash
95 |
96 | curl "http://localhost:3000/bigtable?limit=25" -I \
97 | -H "Prefer: count=planned"
98 |
99 | .. code-block:: http
100 |
101 | HTTP/1.1 206 Partial Content
102 | Content-Range: 0-24/3572000
103 |
104 | Note that the accuracy of this count depends on how up-to-date are the PostgreSQL statistics tables.
105 | For example in this case, to increase the accuracy of the count you can do ``ANALYZE bigtable``.
106 | See `ANALYZE `_ for more details.
107 |
108 | .. _estimated_count:
109 |
110 | Estimated Count
111 | ~~~~~~~~~~~~~~~
112 |
113 | When you are interested in the count, the relative error is important. If you have a :ref:`planned count ` of 1000000 and the exact count is
114 | 1001000, the error is small enough to be ignored. But with a planned count of 7, an exact count of 28 would be a huge misprediction.
115 |
116 | In general, when having smaller row-counts, the estimated count should be as close to the exact count as possible.
117 |
118 | To help with these cases, PostgREST can get the exact count up until a threshold and get the planned count when
119 | that threshold is surpassed. To use this behavior, you can specify the ``Prefer: count=estimated`` header. The **threshold** is
120 | defined by :ref:`db-max-rows`.
121 |
122 | Here's an example. Suppose we set ``db-max-rows=1000`` and ``smalltable`` has 321 rows, then we'll get the exact count:
123 |
124 | .. code-block:: bash
125 |
126 | curl "http://localhost:3000/smalltable?limit=25" -I \
127 | -H "Prefer: count=estimated"
128 |
129 | .. code-block:: http
130 |
131 | HTTP/1.1 206 Partial Content
132 | Content-Range: 0-24/321
133 |
134 | If we make a similar request on ``bigtable``, which has 3573458 rows, we would get the planned count:
135 |
136 | .. code-block:: bash
137 |
138 | curl "http://localhost:3000/bigtable?limit=25" -I \
139 | -H "Prefer: count=estimated"
140 |
141 | .. code-block:: http
142 |
143 | HTTP/1.1 206 Partial Content
144 | Content-Range: 0-24/3572000
145 |
--------------------------------------------------------------------------------
/docs/references/api/preferences.rst:
--------------------------------------------------------------------------------
1 | .. _preferences:
2 |
3 | Prefer Header
4 | #############
5 |
6 | PostgREST honors the Prefer HTTP header specified on `RFC 7240 `_. It allows clients to specify required and optional behaviors for their requests.
7 |
8 | The following preferences are supported.
9 |
10 | - ``Prefer: handling``. See :ref:`prefer_handling`.
11 | - ``Prefer: timezone``. See :ref:`prefer_timezone`.
12 | - ``Prefer: return``. See :ref:`prefer_return`.
13 | - ``Prefer: count``. See :ref:`prefer_count`.
14 | - ``Prefer: resolution``. See :ref:`prefer_resolution`.
15 | - ``Prefer: missing``. See :ref:`bulk_insert_default`.
16 | - ``Prefer: max-affected``, See :ref:`prefer_max_affected`.
17 | - ``Prefer: tx``. See :ref:`prefer_tx`.
18 | - ``Prefer: params``. See :ref:`prefer_params`.
19 |
20 | .. _prefer_handling:
21 |
22 | Strict or Lenient Handling
23 | ==========================
24 |
25 | The server ignores unrecognized or unfulfillable preferences by default. You can control this behavior with the ``handling`` preference. It can take two values: ``lenient`` (the default) or ``strict``.
26 |
27 | ``handling=strict`` will throw an error if you specify invalid preferences. For instance:
28 |
29 | .. code-block:: bash
30 |
31 | curl -i "http://localhost:3000/projects" \
32 | -H "Prefer: handling=strict, foo, bar"
33 |
34 | .. code-block:: http
35 |
36 | HTTP/1.1 400 Bad Request
37 | Content-Type: application/json; charset=utf-8
38 |
39 | .. code-block:: json
40 |
41 | {
42 | "code": "PGRST122",
43 | "message": "Invalid preferences given with handling=strict",
44 | "details": "Invalid preferences: foo, bar",
45 | "hint": null
46 | }
47 |
48 |
49 | ``handling=lenient`` ignores invalid preferences.
50 |
51 | .. code-block:: bash
52 |
53 | curl -i "http://localhost:3000/projects" \
54 | -H "Prefer: handling=lenient, foo, bar"
55 |
56 | .. code-block:: http
57 |
58 | HTTP/1.1 200 OK
59 | Content-Type: application/json; charset=utf-8
60 |
61 | .. _prefer_timezone:
62 |
63 | Timezone
64 | ========
65 |
66 | The ``timezone`` preference allows you to change the `PostgreSQL timezone `_. It accepts all timezones in `pg_timezone_names `_.
67 |
68 |
69 | .. code-block:: bash
70 |
71 | curl -i "http://localhost:3000/timestamps" \
72 | -H "Prefer: timezone=America/Los_Angeles"
73 |
74 | .. code-block:: http
75 |
76 | HTTP/1.1 200 OK
77 | Content-Type: application/json; charset=utf-8
78 | Preference-Applied: timezone=America/Los_Angeles
79 |
80 | .. code-block:: json
81 |
82 | [
83 | {"t":"2023-10-18T05:37:59.611-07:00"},
84 | {"t":"2023-10-18T07:37:59.611-07:00"},
85 | {"t":"2023-10-18T09:37:59.611-07:00"}
86 | ]
87 |
88 | For an invalid timezone, PostgREST returns values with the default timezone (configured on ``postgresql.conf`` or as a setting on the :ref:`authenticator `).
89 |
90 | .. code-block:: bash
91 |
92 | curl -i "http://localhost:3000/timestamps" \
93 | -H "Prefer: timezone=Jupiter/Red_Spot"
94 |
95 | .. code-block:: http
96 |
97 | HTTP/1.1 200 OK
98 | Content-Type: application/json; charset=utf-8
99 |
100 | .. code-block:: json
101 |
102 | [
103 | {"t":"2023-10-18T12:37:59.611+00:00"},
104 | {"t":"2023-10-18T14:37:59.611+00:00"},
105 | {"t":"2023-10-18T16:37:59.611+00:00"}
106 | ]
107 |
108 | Note that there's no ``Preference-Applied`` in the response.
109 |
110 | However, with ``handling=strict``, an invalid timezone preference will throw an :ref:`error `.
111 |
112 | .. code-block:: bash
113 |
114 | curl -i "http://localhost:3000/timestamps" \
115 | -H "Prefer: handling=strict, timezone=Jupiter/Red_Spot"
116 |
117 | .. code-block:: http
118 |
119 | HTTP/1.1 400 Bad Request
120 |
121 | .. _prefer_return:
122 |
123 | Return Representation
124 | =====================
125 |
126 | The ``return`` preference can be used to obtain information about affected resource when it's :ref:`inserted `, :ref:`updated ` or :ref:`deleted `.
127 | This helps avoid a subsequent GET request.
128 |
129 | Minimal
130 | -------
131 |
132 | With ``Prefer: return=minimal``, no response body will be returned. This is the default mode for all write requests.
133 |
134 | Headers Only
135 | ------------
136 |
137 | If the table has a primary key, the response can contain a :code:`Location` header describing where to find the new object by including the header :code:`Prefer: return=headers-only` in the request. Make sure that the table is not write-only, otherwise constructing the :code:`Location` header will cause a permissions error.
138 |
139 | .. code-block:: bash
140 |
141 | curl -i "http://localhost:3000/projects" -X POST \
142 | -H "Content-Type: application/json" \
143 | -H "Prefer: return=headers-only" \
144 | -d '{"id":33, "name": "x"}'
145 |
146 | .. code-block:: http
147 |
148 | HTTP/1.1 201 Created
149 | Location: /projects?id=eq.34
150 | Preference-Applied: return=headers-only
151 |
152 | Full
153 | ----
154 |
155 | On the other end of the spectrum you can get the full created object back in the response to your request by including the header :code:`Prefer: return=representation`. That way you won't have to make another HTTP call to discover properties that may have been filled in on the server side. You can also apply the standard :ref:`v_filter` to these results.
156 |
157 | .. code-block:: bash
158 |
159 | curl -i "http://localhost:3000/projects" -X POST \
160 | -H "Content-Type: application/json" \
161 | -H "Prefer: return=representation" \
162 | -d '{"id":33, "name": "x"}'
163 |
164 | .. code-block:: http
165 |
166 | HTTP/1.1 201 Created
167 | Preference-Applied: return=representation
168 |
169 | .. code-block:: json
170 |
171 | [
172 | {
173 | "id": 33,
174 | "name": "x"
175 | }
176 | ]
177 |
178 | .. _prefer_tx:
179 |
180 | Transaction End Preference
181 | ==========================
182 |
183 | The ``tx`` preference can be set to specify if the :ref:`transaction ` will end in a COMMIT or ROLLBACK. This preference is not enabled by default but can be activated with :ref:`db-tx-end`.
184 |
185 | .. code-block:: bash
186 |
187 | curl -i "http://localhost:3000/projects" -X POST \
188 | -H "Content-Type: application/json" \
189 | -H "Prefer: tx=rollback, return=representation" \
190 | -d '{"name": "Project X"}'
191 |
192 | .. code-block:: http
193 |
194 | HTTP/1.1 200 OK
195 | Preference-Applied: tx=rollback, return=representation
196 |
197 | {"id": 35, "name": "Project X"}
198 |
199 |
200 | .. _prefer_max_affected:
201 |
202 | Max Affected
203 | ============
204 |
205 | You can set a limit to the amount of resources affected in a request by sending ``max-affected`` preference. This feature works in combination with ``handling=strict`` preference. ``max-affected`` would be ignored with lenient handling. The "affected resources" are the number of rows returned by ``DELETE`` and ``PATCH`` requests. This is also supported through ``RPC`` calls.
206 |
207 | To illustrate the use of this preference, consider the following scenario where the ``items`` table contains 14 rows.
208 |
209 | .. code-block:: bash
210 |
211 | curl -i "http://localhost:3000/items?id=lt.15 -X DELETE \
212 | -H "Content-Type: application/json" \
213 | -H "Prefer: handling=strict, max-affected=10"
214 |
215 | .. code-block:: http
216 |
217 | HTTP/1.1 400 Bad Request
218 |
219 | .. code-block:: json
220 |
221 | {
222 | "code": "PGRST124",
223 | "message": "Query result exceeds max-affected preference constraint",
224 | "details": "The query affects 14 rows",
225 | "hint": null
226 | }
227 |
228 | .. _prefer_params:
229 |
230 | Single JSON object as Function Parameter
231 | ----------------------------------------
232 |
233 | .. warning::
234 |
235 | Using this preference is **deprecated** in favor of :ref:`s_proc_single_json`.
236 |
237 | :code:`Prefer: params=single-object` allows sending the JSON request body as the single argument of a :ref:`function `.
238 |
239 | .. code-block:: plpgsql
240 |
241 | CREATE FUNCTION mult_them(param json) RETURNS int AS $$
242 | SELECT (param->>'x')::int * (param->>'y')::int
243 | $$ LANGUAGE SQL;
244 |
245 | .. code-block:: bash
246 |
247 | curl "http://localhost:3000/rpc/mult_them" \
248 | -X POST -H "Content-Type: application/json" \
249 | -H "Prefer: params=single-object" \
250 | -d '{ "x": 4, "y": 2 }'
251 |
252 | .. code-block:: json
253 |
254 | 8
255 |
--------------------------------------------------------------------------------
/docs/references/api/resource_representation.rst:
--------------------------------------------------------------------------------
1 | Resource Representation
2 | #######################
3 |
4 | PostgREST uses proper HTTP content negotiation (`RFC7231 `_) to deliver a resource representation.
5 | That is to say the same API endpoint can respond in different formats like JSON or CSV depending on the request.
6 |
7 | .. _res_format:
8 |
9 | Response Format
10 | ===============
11 |
12 | Use the Accept request header to specify the acceptable format (or formats) for the response:
13 |
14 | .. code-block:: bash
15 |
16 | curl "http://localhost:3000/people" \
17 | -H "Accept: application/json"
18 |
19 | .. _builtin_media:
20 |
21 | Builtin Media Type Handlers
22 | ===========================
23 |
24 | Builtin handlers are offered for common standard media types.
25 |
26 | * ``text/csv`` and ``application/json``, for all API endpoints. See :ref:`tables_views` and :ref:`s_procs`.
27 | * ``application/openapi+json``, for the root endpoint. See :ref:`open-api`.
28 | * ``application/geo+json``, see :ref:`ww_postgis`.
29 | * ``*/*``, resolves to ``application/json`` for API endpoints and to ``application/openapi+json`` for the root endpoint.
30 |
31 | The following vendor media types handlers are also supported.
32 |
33 | * ``application/vnd.pgrst.plan``, see :ref:`explain_plan`.
34 | * ``application/vnd.pgrst.object`` and ``application/vnd.pgrst.array``, see :ref:`singular_plural` and :ref:`stripped_nulls`.
35 |
36 | Any unrecognized media type will throw an error.
37 |
38 | .. code-block:: bash
39 |
40 | curl "http://localhost:3000/people" \
41 | -H "Accept: unknown/unknown"
42 |
43 | .. code-block:: http
44 |
45 | HTTP/1.1 415 Unsupported Media Type
46 |
47 | {"code":"PGRST107","details":null,"hint":null,"message":"None of these media types are available: unknown/unknown"}
48 |
49 | To extend the accepted media types, you can use :ref:`custom_media`.
50 |
51 | .. _singular_plural:
52 |
53 | Singular or Plural
54 | ------------------
55 |
56 | 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
57 |
58 | .. code:: json
59 |
60 | [
61 | { "id": 1 }
62 | ]
63 |
64 | This can be inconvenient for client code. To return the first result as an object unenclosed by an array, specify :code:`vnd.pgrst.object` as part of the :code:`Accept` header
65 |
66 | .. code-block:: bash
67 |
68 | curl "http://localhost:3000/items?id=eq.1" \
69 | -H "Accept: application/vnd.pgrst.object+json"
70 |
71 | This returns
72 |
73 | .. code:: json
74 |
75 | { "id": 1 }
76 |
77 | with a :code:`Content-Type: application/vnd.pgrst.object+json`.
78 |
79 | When a singular response is requested but no entries are found, the server responds with an error message and 406 Not Acceptable status code rather than the usual empty array and 200 status:
80 |
81 | .. code-block:: json
82 |
83 | {
84 | "message": "JSON object requested, multiple (or no) rows returned",
85 | "details": "Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row",
86 | "hint": null,
87 | "code": "PGRST505"
88 | }
89 |
90 | .. note::
91 |
92 | 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 singular 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.
93 |
94 | 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.
95 |
96 | .. _stripped_nulls:
97 |
98 | Stripped Nulls
99 | --------------
100 |
101 | By default PostgREST returns all JSON null values. For example, requesting ``/projects?id=gt.10`` returns
102 |
103 | .. code:: json
104 |
105 | [
106 | { "id": 11, "name": "OSX", "client_id": 1, "another_col": "val" },
107 | { "id": 12, "name": "ProjectX", "client_id": null, "another_col": null },
108 | { "id": 13, "name": "Y", "client_id": null, "another_col": null }
109 | ]
110 |
111 | On large result sets, the unused keys with ``null`` values can waste bandwith unnecessarily. To remove them, specify ``nulls=stripped`` as a parameter of ``application/vnd.pgrst.array``:
112 |
113 | .. code-block:: bash
114 |
115 | curl "http://localhost:3000/projects?id=gt.10" \
116 | -H "Accept: application/vnd.pgrst.array+json;nulls=stripped"
117 |
118 | This returns
119 |
120 | .. code:: json
121 |
122 | [
123 | { "id": 11, "name": "OSX", "client_id": 1, "another_col": "val" },
124 | { "id": 12, "name": "ProjectX" },
125 | { "id": 13, "name": "Y"}
126 | ]
127 |
128 | .. _req_body:
129 |
130 | Request Body
131 | ============
132 |
133 | The server handles the following request body media types:
134 |
135 | * ``application/json``
136 | * ``application/x-www-form-urlencoded``
137 | * ``text/csv``
138 |
139 | For :ref:`tables_views` this works on ``POST``, ``PATCH`` and ``PUT`` methods. For :ref:`s_procs`, it works on ``POST`` methods.
140 |
141 | For stored procedures there are three additional types:
142 |
143 | * ``application/octet-stream``
144 | * ``text/plain``
145 | * ``text/xml``
146 |
147 | See :ref:`s_proc_single_unnamed`.
148 |
--------------------------------------------------------------------------------
/docs/references/api/schemas.rst:
--------------------------------------------------------------------------------
1 | .. _schemas:
2 |
3 | Schemas
4 | =======
5 |
6 | PostgREST can expose a single or multiple schema's tables, views and functions. The :ref:`active database role ` must have the usage privilege on the schemas to access them.
7 |
8 | Single schema
9 | -------------
10 |
11 | To expose a single schema, specify a single value in :ref:`db-schemas`.
12 |
13 | .. code:: bash
14 |
15 | db-schemas = "api"
16 |
17 | This schema is added to the `search_path `_ of every request using :ref:`tx_settings`.
18 |
19 | .. _multiple-schemas:
20 |
21 | Multiple schemas
22 | ----------------
23 |
24 | To expose multiple schemas, specify a comma-separated list on :ref:`db-schemas`:
25 |
26 | .. code:: bash
27 |
28 | db-schemas = "tenant1, tenant2"
29 |
30 | To switch schemas, use the ``Accept-Profile`` and ``Content-Profile`` headers.
31 |
32 | If you don't specify a Profile header, the first schema in the list(``tenant1`` here) is selected as the default schema.
33 |
34 | Only the selected schema gets added to the `search_path `_ of every request.
35 |
36 | .. note::
37 |
38 | These headers are based on the "Content Negotiation by Profile" spec: https://www.w3.org/TR/dx-prof-conneg
39 |
40 | GET/HEAD
41 | ~~~~~~~~
42 |
43 | For GET or HEAD, select the schema with ``Accept-Profile``.
44 |
45 | .. code-block:: bash
46 |
47 | curl "http://localhost:3000/items" \
48 | -H "Accept-Profile: tenant2"
49 |
50 | Other methods
51 | ~~~~~~~~~~~~~
52 |
53 | For POST, PATCH, PUT and DELETE, select the schema with ``Content-Profile``.
54 |
55 | .. code-block:: bash
56 |
57 | curl "http://localhost:3000/items" \
58 | -X POST -H "Content-Type: application/json" \
59 | -H "Content-Profile: tenant2" \
60 | -d '{...}'
61 |
62 | You can also select the schema for :ref:`s_procs` and :ref:`open-api`.
63 |
64 | Restricted schemas
65 | ~~~~~~~~~~~~~~~~~~
66 |
67 | You can only switch to a schema included in :ref:`db-schemas`. Using another schema will result in an error:
68 |
69 | .. code-block:: bash
70 |
71 | curl "http://localhost:3000/items" \
72 | -H "Accept-Profile: tenant3"
73 |
74 | .. code-block::
75 |
76 | {
77 | "code":"PGRST106",
78 | "details":null,
79 | "hint":null,
80 | "message":"The schema must be one of the following: tenant1, tenant2"
81 | }
82 |
83 |
84 | Dynamic schemas
85 | ~~~~~~~~~~~~~~~
86 |
87 | To add schemas dynamically, you can use :ref:`in_db_config` plus :ref:`config reloading ` and :ref:`schema cache reloading `. Here are some options for how to do this:
88 |
89 | - If the schemas' names have a pattern, like a ``tenant_`` prefix, do:
90 |
91 | .. code-block:: postgresql
92 |
93 | create or replace function postgrest.pre_config()
94 | returns void as $$
95 | select
96 | set_config('pgrst.db_schemas', string_agg(nspname, ','), true)
97 | from pg_namespace
98 | where nspname like 'tenant_%';
99 | $$ language sql;
100 |
101 | - If there's no name pattern but they're created with a particular role (``CREATE SCHEMA mine AUTHORIZATION joe``), do:
102 |
103 | .. code-block:: postgresql
104 |
105 | create or replace function postgrest.pre_config()
106 | returns void as $$
107 | select
108 | set_config('pgrst.db_schemas', string_agg(nspname, ','), true)
109 | from pg_namespace
110 | where nspowner = 'joe'::regrole;
111 | $$ language sql;
112 |
113 | - Otherwise, you might need to create a table that stores the allowed schemas.
114 |
115 | .. code-block:: postgresql
116 |
117 | create table postgrest.config (schemas text);
118 |
119 | create or replace function postgrest.pre_config()
120 | returns void as $$
121 | select
122 | set_config('pgrst.db_schemas', schemas, true)
123 | from postgrest.config;
124 | $$ language sql;
125 |
126 | Then each time you add an schema, do:
127 |
128 | .. code-block:: postgresql
129 |
130 | NOTIFY pgrst, 'reload config';
131 | NOTIFY pgrst, 'reload schema';
132 |
--------------------------------------------------------------------------------
/docs/references/api/stored_procedures.rst:
--------------------------------------------------------------------------------
1 | .. _s_procs:
2 |
3 | Stored Procedures
4 | =================
5 |
6 | *"A single resource can be the equivalent of a database stored procedure, with the power to abstract state changes over any number of storage items"* -- `Roy T. Fielding `_
7 |
8 | Procedures can perform any operations allowed by PostgreSQL (read data, modify data, :ref:`raise errors `, and even DDL operations). Every stored procedure in the :ref:`exposed schema ` and accessible by the :ref:`active database role ` is executable under the :code:`/rpc` prefix.
9 |
10 | If they return table types, Stored Procedures can:
11 |
12 | - Use all the same :ref:`read filters as Tables and Views ` (horizontal/vertical filtering, counts, limits, etc.).
13 | - Use :ref:`Resource Embedding `, if the returned table type has relationships to other tables.
14 |
15 | .. note::
16 |
17 | Why the ``/rpc`` prefix? PostgreSQL allows a table or view to have the same name as a function. The prefix allows us to avoid routes collisions.
18 |
19 | Calling with POST
20 | -----------------
21 |
22 | To supply arguments in an API call, include a JSON object in the request payload. Each key/value of the object will become an argument.
23 |
24 | For instance, assume we have created this function in the database.
25 |
26 | .. code-block:: plpgsql
27 |
28 | CREATE FUNCTION add_them(a integer, b integer)
29 | RETURNS integer AS $$
30 | SELECT a + b;
31 | $$ LANGUAGE SQL IMMUTABLE;
32 |
33 | .. important::
34 |
35 | Whenever you create or change a function you must refresh PostgREST's schema cache. See the section :ref:`schema_reloading`.
36 |
37 | The client can call it by posting an object like
38 |
39 | .. code-block:: bash
40 |
41 | curl "http://localhost:3000/rpc/add_them" \
42 | -X POST -H "Content-Type: application/json" \
43 | -d '{ "a": 1, "b": 2 }'
44 |
45 | .. code-block:: json
46 |
47 | 3
48 |
49 | .. note::
50 |
51 | PostgreSQL converts identifier names to lowercase unless you quote them like:
52 |
53 | .. code-block:: postgres
54 |
55 | CREATE FUNCTION "someFunc"("someParam" text) ...
56 |
57 | Calling with GET
58 | ----------------
59 |
60 | If the function doesn't modify the database, it will also run under the GET method (see :ref:`access_mode`).
61 |
62 | .. code-block:: bash
63 |
64 | curl "http://localhost:3000/rpc/add_them?a=1&b=2"
65 |
66 | The function parameter names match the JSON object keys in the POST case, for the GET case they match the query parameters ``?a=1&b=2``.
67 |
68 | .. _s_proc_single_json:
69 |
70 | Functions with a single unnamed JSON parameter
71 | ----------------------------------------------
72 |
73 | If you want the JSON request body to be sent as a single argument, you can create a function with a single unnamed ``json`` or ``jsonb`` parameter.
74 | For this the ``Content-Type: application/json`` header must be included in the request.
75 |
76 | .. code-block:: plpgsql
77 |
78 | CREATE FUNCTION mult_them(json) RETURNS int AS $$
79 | SELECT ($1->>'x')::int * ($1->>'y')::int
80 | $$ LANGUAGE SQL;
81 |
82 | .. code-block:: bash
83 |
84 | curl "http://localhost:3000/rpc/mult_them" \
85 | -X POST -H "Content-Type: application/json" \
86 | -d '{ "x": 4, "y": 2 }'
87 |
88 | .. code-block:: json
89 |
90 | 8
91 |
92 | .. note::
93 |
94 | If an overloaded function has a single ``json`` or ``jsonb`` unnamed parameter, PostgREST will call this function as a fallback provided that no other overloaded function is found with the parameters sent in the POST request.
95 |
96 | .. warning::
97 |
98 | Sending the JSON request body as a single argument is also possible with :ref:`Prefer: params=single-object ` but this method is **deprecated**.
99 |
100 | .. _s_proc_single_unnamed:
101 |
102 | Functions with a single unnamed parameter
103 | -----------------------------------------
104 |
105 | You can make a POST request to a function with a single unnamed parameter to send raw ``bytea``, ``text`` or ``xml`` data.
106 |
107 | To send raw XML, the parameter type must be ``xml`` and the header ``Content-Type: text/xml`` must be included in the request.
108 |
109 | To send raw binary, the parameter type must be ``bytea`` and the header ``Content-Type: application/octet-stream`` must be included in the request.
110 |
111 | .. code-block:: plpgsql
112 |
113 | CREATE TABLE files(blob bytea);
114 |
115 | CREATE FUNCTION upload_binary(bytea) RETURNS void AS $$
116 | INSERT INTO files(blob) VALUES ($1);
117 | $$ LANGUAGE SQL;
118 |
119 | .. code-block:: bash
120 |
121 | curl "http://localhost:3000/rpc/upload_binary" \
122 | -X POST -H "Content-Type: application/octet-stream" \
123 | --data-binary "@file_name.ext"
124 |
125 | .. code-block:: http
126 |
127 | HTTP/1.1 200 OK
128 |
129 | [ ... ]
130 |
131 | To send raw text, the parameter type must be ``text`` and the header ``Content-Type: text/plain`` must be included in the request.
132 |
133 | .. _s_procs_array:
134 |
135 | Functions with array parameters
136 | -------------------------------
137 |
138 | You can call a function that takes an array parameter:
139 |
140 | .. code-block:: postgres
141 |
142 | create function plus_one(arr int[]) returns int[] as $$
143 | SELECT array_agg(n + 1) FROM unnest($1) AS n;
144 | $$ language sql;
145 |
146 | .. code-block:: bash
147 |
148 | curl "http://localhost:3000/rpc/plus_one" \
149 | -X POST -H "Content-Type: application/json" \
150 | -d '{"arr": [1,2,3,4]}'
151 |
152 | .. code-block:: json
153 |
154 | [2,3,4,5]
155 |
156 | For calling the function with GET, you can pass the array as an `array literal `_,
157 | as in ``{1,2,3,4}``. Note that the curly brackets have to be urlencoded(``{`` is ``%7B`` and ``}`` is ``%7D``).
158 |
159 | .. code-block:: bash
160 |
161 | curl "http://localhost:3000/rpc/plus_one?arr=%7B1,2,3,4%7D'"
162 |
163 | .. note::
164 |
165 | For versions prior to PostgreSQL 10, to pass a PostgreSQL native array on a POST payload, you need to quote it and use an array literal:
166 |
167 | .. code-block:: bash
168 |
169 | curl "http://localhost:3000/rpc/plus_one" \
170 | -X POST -H "Content-Type: application/json" \
171 | -d '{ "arr": "{1,2,3,4}" }'
172 |
173 | In these versions we recommend using function parameters of type JSON to accept arrays from the client.
174 |
175 | .. _s_procs_variadic:
176 |
177 | Variadic functions
178 | ------------------
179 |
180 | You can call a variadic function by passing a JSON array in a POST request:
181 |
182 | .. code-block:: postgres
183 |
184 | create function plus_one(variadic v int[]) returns int[] as $$
185 | SELECT array_agg(n + 1) FROM unnest($1) AS n;
186 | $$ language sql;
187 |
188 | .. code-block:: bash
189 |
190 | curl "http://localhost:3000/rpc/plus_one" \
191 | -X POST -H "Content-Type: application/json" \
192 | -d '{"v": [1,2,3,4]}'
193 |
194 | .. code-block:: json
195 |
196 | [2,3,4,5]
197 |
198 | In a GET request, you can repeat the same parameter name:
199 |
200 | .. code-block:: bash
201 |
202 | curl "http://localhost:3000/rpc/plus_one?v=1&v=2&v=3&v=4"
203 |
204 | Repeating also works in POST requests with ``Content-Type: application/x-www-form-urlencoded``:
205 |
206 | .. code-block:: bash
207 |
208 | curl "http://localhost:3000/rpc/plus_one" \
209 | -X POST -H "Content-Type: application/x-www-form-urlencoded" \
210 | -d 'v=1&v=2&v=3&v=4'
211 |
212 | .. _table_functions:
213 |
214 | Table-Valued Functions
215 | ----------------------
216 |
217 | A function that returns a table type can be filtered using the same filters as :ref:`tables and views `. They can also use :ref:`Resource Embedding `.
218 |
219 | .. code-block:: postgres
220 |
221 | CREATE FUNCTION best_films_2017() RETURNS SETOF films ..
222 |
223 | .. code-block:: bash
224 |
225 | curl "http://localhost:3000/rpc/best_films_2017?select=title,director:directors(*)"
226 |
227 | .. code-block:: bash
228 |
229 | curl "http://localhost:3000/rpc/best_films_2017?rating=gt.8&order=title.desc"
230 |
231 | .. _function_inlining:
232 |
233 | Function Inlining
234 | ~~~~~~~~~~~~~~~~~
235 |
236 | A function that follows the `rules for inlining `_ will also inline :ref:`filters `, :ref:`order ` and :ref:`limits `.
237 |
238 | For example, for the following function:
239 |
240 | .. code-block:: postgres
241 |
242 | create function getallprojects() returns setof projects
243 | language sql stable
244 | as $$
245 | select * from projects;
246 | $$;
247 |
248 | Let's get its :ref:`explain_plan` when calling it with filters applied:
249 |
250 | .. code-block:: bash
251 |
252 | curl "http://localhost:3000/rpc/getallprojects?id=eq.1" \
253 | -H "Accept: application/vnd.pgrst.plan"
254 |
255 | .. code-block:: psql
256 |
257 | Aggregate (cost=8.18..8.20 rows=1 width=112)
258 | -> Index Scan using projects_pkey on projects (cost=0.15..8.17 rows=1 width=40)
259 | Index Cond: (id = 1)
260 |
261 | Notice there's no "Function Scan" node in the plan, which tells us it has been inlined.
262 |
263 | .. _scalar_functions:
264 |
265 | Scalar functions
266 | ----------------
267 |
268 | PostgREST will detect if the function is scalar or table-valued and will shape the response format accordingly:
269 |
270 | .. code-block:: bash
271 |
272 | curl "http://localhost:3000/rpc/add_them?a=1&b=2"
273 |
274 | .. code-block:: json
275 |
276 | 3
277 |
278 | .. code-block:: bash
279 |
280 | curl "http://localhost:3000/rpc/best_films_2017"
281 |
282 | .. code-block:: json
283 |
284 | [
285 | { "title": "Okja", "rating": 7.4},
286 | { "title": "Call me by your name", "rating": 8},
287 | { "title": "Blade Runner 2049", "rating": 8.1}
288 | ]
289 |
290 | To manually choose a return format such as binary, see :ref:`custom_media`.
291 |
292 | .. _untyped_functions:
293 |
294 | Untyped functions
295 | -----------------
296 |
297 | Functions that return ``record`` or ``SETOF record`` are supported:
298 |
299 | .. code-block:: postgres
300 |
301 | create function projects_setof_record() returns setof record as $$
302 | select * from projects;
303 | $$ language sql;
304 |
305 | .. code-block:: bash
306 |
307 | curl "http://localhost:3000/rpc/projects_setof_record"
308 |
309 | .. code-block:: json
310 |
311 | [{"id":1,"name":"Windows 7","client_id":1},
312 | {"id":2,"name":"Windows 10","client_id":1},
313 | {"id":3,"name":"IOS","client_id":2}]
314 |
315 | However note that they will fail when trying to use :ref:`v_filter` and :ref:`h_filter` on them.
316 |
317 | So while they can be used for quick tests, it's recommended to always choose a strict return type for the function.
318 |
319 | Overloaded functions
320 | --------------------
321 |
322 | You can call overloaded functions with different number of arguments.
323 |
324 | .. code-block:: postgres
325 |
326 | CREATE FUNCTION rental_duration(customer_id integer) ..
327 |
328 | CREATE FUNCTION rental_duration(customer_id integer, from_date date) ..
329 |
330 | .. code-block:: bash
331 |
332 | curl "http://localhost:3000/rpc/rental_duration?customer_id=232"
333 |
334 | .. code-block:: bash
335 |
336 | curl "http://localhost:3000/rpc/rental_duration?customer_id=232&from_date=2018-07-01"
337 |
338 | .. important::
339 |
340 | Overloaded functions with the same argument names but different types are not supported.
341 |
--------------------------------------------------------------------------------
/docs/references/api/url_grammar.rst:
--------------------------------------------------------------------------------
1 | .. note::
2 |
3 | This page is a work in progress.
4 |
5 | .. _url_grammar:
6 |
7 | URL Grammar
8 | ===========
9 |
10 | .. _custom_queries:
11 |
12 | Custom Queries
13 | --------------
14 |
15 | 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
16 |
17 | * Table unions
18 | * More complicated joins than those provided by :ref:`resource_embedding`.
19 | * Geo-spatial queries that require an argument, like "points near (lat,lon)"
20 |
21 | Unicode support
22 | ---------------
23 |
24 | PostgREST supports unicode in schemas, tables, columns and values. To access a table with unicode name, use percent encoding.
25 |
26 | To request this:
27 |
28 | .. code-block:: http
29 |
30 | GET /موارد HTTP/1.1
31 |
32 | Do this:
33 |
34 | .. code-block:: bash
35 |
36 | curl "http://localhost:3000/%D9%85%D9%88%D8%A7%D8%B1%D8%AF"
37 |
38 | .. _tabs-cols-w-spaces:
39 |
40 | Table / Columns with spaces
41 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
42 |
43 | You can request table/columns with spaces in them by percent encoding the spaces with ``%20``:
44 |
45 | .. code-block:: bash
46 |
47 | curl "http://localhost:3000/Order%20Items?Unit%20Price=lt.200"
48 |
49 | .. _reserved-chars:
50 |
51 | Reserved characters
52 | ~~~~~~~~~~~~~~~~~~~
53 |
54 | If filters include PostgREST reserved characters(``,``, ``.``, ``:``, ``()``) you'll have to surround them in percent encoded double quotes ``%22`` for correct processing.
55 |
56 | Here ``Hebdon,John`` and ``Williams,Mary`` are values.
57 |
58 | .. code-block:: bash
59 |
60 | curl "http://localhost:3000/employees?name=in.(%22Hebdon,John%22,%22Williams,Mary%22)"
61 |
62 | Here ``information.cpe`` is a column name.
63 |
64 | .. code-block:: bash
65 |
66 | curl "http://localhost:3000/vulnerabilities?%22information.cpe%22=like.*MS*"
67 |
68 | If the value filtered by the ``in`` operator has a double quote (``"``), you can escape it using a backslash ``"\""``. A backslash itself can be used with a double backslash ``"\\"``.
69 |
70 | Here ``Quote:"`` and ``Backslash:\`` are percent-encoded values. Note that ``%5C`` is the percent-encoded backslash.
71 |
72 | .. code-block:: bash
73 |
74 | curl "http://localhost:3000/marks?name=in.(%22Quote:%5C%22%22,%22Backslash:%5C%5C%22)"
75 |
76 | .. note::
77 |
78 | Some HTTP libraries might encode URLs automatically(e.g. :code:`axios`). In these cases you should use double quotes
79 | :code:`""` directly instead of :code:`%22`.
80 |
--------------------------------------------------------------------------------
/docs/references/auth.rst:
--------------------------------------------------------------------------------
1 | .. _authn:
2 |
3 | Authentication
4 | ==============
5 |
6 | PostgREST is designed to keep the database at the center of API security. All :ref:`authorization happens in the database ` . It is PostgREST's job to **authenticate** requests -- i.e. verify that a client is who they say they are -- and then let the database **authorize** client actions.
7 |
8 | .. _roles:
9 |
10 | Overview of role system
11 | -----------------------
12 |
13 | There are three types of roles used by PostgREST, the **authenticator**, **anonymous** and **user** roles. The database administrator creates these roles and configures PostgREST to use them.
14 |
15 | .. image:: ../_static/security-roles.png
16 |
17 | The authenticator role is used for connecting to the database and should be configured to have very limited access. It is a chameleon whose job is to "become" other users to service authenticated HTTP requests.
18 |
19 |
20 | .. code:: sql
21 |
22 |
23 | CREATE ROLE authenticator LOGIN NOINHERIT NOCREATEDB NOCREATEROLE NOSUPERUSER;
24 | CREATE ROLE anonymous NOLOGIN;
25 | CREATE ROLE webuser NOLOGIN;
26 |
27 | .. note::
28 |
29 | The names "authenticator" and "anon" names are configurable and not sacred, we simply choose them for clarity. See :ref:`db-uri` and :ref:`db-anon-role`.
30 |
31 | .. _user_impersonation:
32 |
33 | User Impersonation
34 | ------------------
35 |
36 | The picture below shows how the server handles authentication. If auth succeeds, it switches into the user role specified by the request, otherwise it switches into the anonymous role (if it's set in :ref:`db-anon-role`).
37 |
38 | .. image:: ../_static/security-anon-choice.png
39 |
40 | This role switching mechanism is called **user impersonation**. In PostgreSQL it's done with the ``SET ROLE`` statement.
41 |
42 | .. note::
43 |
44 | The impersonated roles will have their settings applied. See :ref:`impersonated_settings`.
45 |
46 | .. _jwt_impersonation:
47 |
48 | JWT-Based User Impersonation
49 | ----------------------------
50 |
51 | We use `JSON Web Tokens `_ to authenticate API requests, this allows us to be stateless and not require database lookups for verification. As you'll recall a JWT contains a list of cryptographically signed claims. All claims are allowed but PostgREST cares specifically about a claim called role.
52 |
53 | .. code:: json
54 |
55 | {
56 | "role": "user123"
57 | }
58 |
59 | When a request contains a valid JWT with a role claim PostgREST will switch to the database role with that name for the duration of the HTTP request.
60 |
61 | .. code:: sql
62 |
63 | SET LOCAL ROLE user123;
64 |
65 | Note that the database administrator must allow the authenticator role to switch into this user by previously executing
66 |
67 | .. code:: sql
68 |
69 | GRANT user123 TO authenticator;
70 | -- similarly for the anonymous role
71 | -- GRANT anonymous TO authenticator;
72 |
73 | If the client included no JWT (or one without a role claim) then PostgREST switches into the anonymous role. The database administrator must set the anonymous role permissions correctly to prevent anonymous users from seeing or changing things they shouldn't.
74 |
75 | .. _jwt_generation:
76 |
77 | JWT Generation
78 | ~~~~~~~~~~~~~~
79 |
80 | You can create a valid JWT either from inside your database (see :ref:`sql_user_management`) or via an external service (see :ref:`external_jwt`).
81 |
82 | .. _client_auth:
83 |
84 | Client Auth
85 | ~~~~~~~~~~~
86 |
87 | To make an authenticated request the client must include an :code:`Authorization` HTTP header with the value :code:`Bearer `. For instance:
88 |
89 | .. code-block:: bash
90 |
91 | curl "http://localhost:3000/foo" \
92 | -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4"
93 |
94 | The ``Bearer`` header value can be used with or without capitalization(``bearer``).
95 |
96 | .. _jwt_caching:
97 |
98 | JWT Caching
99 | -----------
100 |
101 | PostgREST validates ``JWTs`` on every request. We can cache ``JWTs`` to avoid this performance overhead.
102 |
103 | To enable JWT caching, the config :code:`jwt-cache-max-lifetime` is to be set. It is the maximum number of seconds for which the cache stores the JWT validation results. The cache uses the :code:`exp` claim to set the cache entry lifetime. If the JWT does not have an :code:`exp` claim, it uses the config value. See :ref:`jwt-cache-max-lifetime` for more details.
104 |
105 | .. note::
106 |
107 | You can use the :ref:`server-timing_header` to see the effect of JWT caching.
108 |
109 | Symmetric Keys
110 | ~~~~~~~~~~~~~~
111 |
112 | Each token is cryptographically signed with a secret key. In the case of symmetric cryptography the signer and verifier share the same secret passphrase, which can be configured with :ref:`jwt-secret`.
113 | If it is set to a simple string value like “reallyreallyreallyreallyverysafe” then PostgREST interprets it as an HMAC-SHA256 passphrase.
114 |
115 | .. _asym_keys:
116 |
117 | Asymmetric Keys
118 | ~~~~~~~~~~~~~~~
119 |
120 | In asymmetric cryptography the signer uses the private key and the verifier the public key.
121 |
122 | As described in the :ref:`configuration` section, PostgREST accepts a ``jwt-secret`` config file parameter. However you can also specify a literal JSON Web Key (JWK) or set. For example, you can use an RSA-256 public key encoded as a JWK:
123 |
124 | .. code-block:: json
125 |
126 | {
127 | "alg":"RS256",
128 | "e":"AQAB",
129 | "key_ops":["verify"],
130 | "kty":"RSA",
131 | "n":"9zKNYTaYGfGm1tBMpRT6FxOYrM720GhXdettc02uyakYSEHU2IJz90G_MLlEl4-WWWYoS_QKFupw3s7aPYlaAjamG22rAnvWu-rRkP5sSSkKvud_IgKL4iE6Y2WJx2Bkl1XUFkdZ8wlEUR6O1ft3TS4uA-qKifSZ43CahzAJyUezOH9shI--tirC028lNg767ldEki3WnVr3zokSujC9YJ_9XXjw2hFBfmJUrNb0-wldvxQbFU8RPXip-GQ_JPTrCTZhrzGFeWPvhA6Rqmc3b1PhM9jY7Dur1sjYWYVyXlFNCK3c-6feo5WlRfe1aCWmwZQh6O18eTmLeT4nWYkDzQ"
132 | }
133 |
134 | .. note::
135 |
136 | This could also be a JSON Web Key Set (JWKS) if it was contained within an array assigned to a `keys` member, e.g. ``{ keys: [jwk1, jwk2] }``.
137 |
138 | Just pass it in as a single line string, escaping the quotes:
139 |
140 | .. code-block:: ini
141 |
142 | jwt-secret = "{ \"alg\":\"RS256\", … }"
143 |
144 | To generate such a public/private key pair use a utility like `latchset/jose `_.
145 |
146 | .. code-block:: bash
147 |
148 | jose jwk gen -i '{"alg": "RS256"}' -o rsa.jwk
149 | jose jwk pub -i rsa.jwk -o rsa.jwk.pub
150 |
151 | # now rsa.jwk.pub contains the desired JSON object
152 |
153 | You can specify the literal value as we saw earlier, or reference a filename to load the JWK from a file:
154 |
155 | .. code-block:: ini
156 |
157 | jwt-secret = "@rsa.jwk.pub"
158 |
159 | JWT Claims Validation
160 | ~~~~~~~~~~~~~~~~~~~~~
161 |
162 | PostgREST honors the :code:`exp` claim for token expiration, rejecting expired tokens.
163 |
164 | JWT Security
165 | ~~~~~~~~~~~~
166 |
167 | There are at least three types of common critiques against using JWT: 1) against the standard itself, 2) against using libraries with known security vulnerabilities, and 3) against using JWT for web sessions. We'll briefly explain each critique, how PostgREST deals with it, and give recommendations for appropriate user action.
168 |
169 | The critique against the `JWT standard `_ is voiced in detail `elsewhere on the web `_. The most relevant part for PostgREST is the so-called :code:`alg=none` issue. Some servers implementing JWT allow clients to choose the algorithm used to sign the JWT. In this case, an attacker could set the algorithm to :code:`none`, remove the need for any signature at all and gain unauthorized access. The current implementation of PostgREST, however, does not allow clients to set the signature algorithm in the HTTP request, making this attack irrelevant. The critique against the standard is that it requires the implementation of the :code:`alg=none` at all.
170 |
171 | Critiques against JWT libraries are only relevant to PostgREST via the library it uses. As mentioned above, not allowing clients to choose the signature algorithm in HTTP requests removes the greatest risk. Another more subtle attack is possible where servers use asymmetric algorithms like RSA for signatures. Once again this is not relevant to PostgREST since it is not supported. Curious readers can find more information in `this article `_. Recommendations about high quality libraries for usage in API clients can be found on `jwt.io `_.
172 |
173 | The last type of critique focuses on the misuse of JWT for maintaining web sessions. The basic recommendation is to `stop using JWT for sessions `_ because most, if not all, solutions to the problems that arise when you do, `do not work `_. The linked articles discuss the problems in depth but the essence of the problem is that JWT is not designed to be secure and stateful units for client-side storage and therefore not suited to session management.
174 |
175 | PostgREST uses JWT mainly for authentication and authorization purposes and encourages users to do the same. For web sessions, using cookies over HTTPS is good enough and well catered for by standard web frameworks.
176 |
177 | .. _custom_validation:
178 |
179 | Custom Validation
180 | -----------------
181 |
182 | PostgREST does not enforce any extra constraints besides JWT validation. An example of an extra constraint would be to immediately revoke access for a certain user. Using :ref:`db-pre-request` you can specify a stored procedure to call immediately after :ref:`user_impersonation` and before the main query itself runs.
183 |
184 | .. code:: ini
185 |
186 | db-pre-request = "public.check_user"
187 |
188 | In the function you can run arbitrary code to check the request and raise an exception(see :ref:`raise_error`) to block it if desired. Here you can take advantage of :ref:`guc_req_headers_cookies_claims` for
189 | doing custom logic based on the web user info.
190 |
191 | .. code-block:: postgres
192 |
193 | CREATE OR REPLACE FUNCTION check_user() RETURNS void AS $$
194 | DECLARE
195 | email text := current_setting('request.jwt.claims', true)::json->>'email';
196 | BEGIN
197 | IF email = 'evil.user@malicious.com' THEN
198 | RAISE EXCEPTION 'No, you are evil'
199 | USING HINT = 'Stop being so evil and maybe you can log in';
200 | END IF;
201 | END
202 | $$ LANGUAGE plpgsql;
203 |
--------------------------------------------------------------------------------
/docs/references/connection_pool.rst:
--------------------------------------------------------------------------------
1 | .. _connection_pool:
2 |
3 | Connection Pool
4 | ===============
5 |
6 | A connection pool is a cache of reusable database connections. It allows serving many HTTP requests using few database connections. Every request to an :doc:`API resource ` borrows a connection from the pool to start a :doc:`transaction `.
7 |
8 | Minimizing connections is paramount to performance. Each PostgreSQL connection creates a process, having too many can exhaust available resources.
9 |
10 | Connection String
11 | -----------------
12 |
13 | For connecting to the database, the pool requires a connection string. You can configure it using :ref:`db-uri`.
14 |
15 | .. _pool_growth_limit:
16 | .. _dyn_conn_pool:
17 |
18 | Dynamic Connection Pool
19 | -----------------------
20 |
21 | To conserve system resources, PostgREST uses a dynamic connection pool. This enables the number of connections in the pool to increase and decrease depending on request traffic.
22 |
23 | - If all the connections are being used, a new connection is added. The pool can grow until it reaches the :ref:`db-pool` size. Note that it’s pointless to set this higher than the ``max_connections`` setting in your database.
24 | - If a connection is unused for a period of time (:ref:`db-pool-max-idletime`), it will be released.
25 |
26 | Connection lifetime
27 | -------------------
28 |
29 | Long-lived PostgreSQL connections can consume considerable memory (see `here `_ for more details).
30 | Under a busy system, the :ref:`db-pool-max-idletime` won't be reached and the connection pool can be full of long-lived connections.
31 |
32 | To avoid this problem and save resources, a connection max lifetime (:ref:`db-pool-max-lifetime`) is enforced.
33 | After the max lifetime is reached, connections from the pool will be released and new ones will be created. This doesn't affect running requests, only unused connections will be released.
34 |
35 | Acquisition Timeout
36 | -------------------
37 |
38 | If all the available connections in the pool are busy, an HTTP request will wait until reaching a timeout (:ref:`db-pool-acquisition-timeout`).
39 |
40 | If the request reaches the timeout, it will be aborted with the following response:
41 |
42 | .. code-block:: http
43 |
44 | HTTP/1.1 504 Gateway Timeout
45 |
46 | {"code":"PGRST003",
47 | "details":null,
48 | "hint":null,
49 | "message":"Timed out acquiring connection from connection pool."}
50 |
51 | .. important::
52 |
53 | Getting this error message is an indicator of a performance issue. To solve it, you can:
54 |
55 | - Reduce your queries execution time.
56 |
57 | - Check the request :ref:`explain_plan` to tune your query, this usually means adding indexes.
58 |
59 | - Reduce the amount of requests.
60 |
61 | - Reduce write requests. Do :ref:`bulk_insert` (or :ref:`upsert`) instead of inserting rows one by one.
62 | - Reduce read requests. Use :ref:`resource_embedding`. Combine unrelated data into a single request using custom database views or functions.
63 | - Use :ref:`s_procs` for combining read and write logic into a single request.
64 |
65 | - Increase the :ref:`db-pool` size.
66 |
67 | - Not a panacea since connections can't grow infinitely. Try the previous recommendations before this.
68 |
69 | .. _automatic_recovery:
70 |
71 | Automatic Recovery
72 | ------------------
73 |
74 | The server will retry reconnecting to the database if connection loss happens.
75 |
76 | - It will retry forever with exponential backoff, with a maximum backoff time of 32 seconds between retries. Each of these attempts are :ref:`logged `.
77 | - It will only stop retrying if the server deems the error to be fatal. This can be a password authentication failure or an internal error.
78 | - The retries happen immediately after a connection loss, if :ref:`db-channel-enabled` is set to true (the default). Otherwise they'll happen once a request arrives.
79 | - To ensure a valid state, the server reloads the :ref:`schema_cache` and :ref:`configuration` when recovering.
80 | - To notify the client of the next retry, the server sends a ``503 Service Unavailable`` status with the ``Retry-After: x`` header. Where ``x`` is the number of seconds programmed for the next retry.
81 | - Automatic recovery can be disabled by setting :ref:`db-pool-automatic-recovery` to ``false``.
82 |
83 | .. _external_connection_poolers:
84 |
85 | Using External Connection Poolers
86 | ---------------------------------
87 |
88 | It's possible to use external connection poolers, such as PgBouncer. Session pooling is compatible, while transaction pooling requires :ref:`db-prepared-statements` set to ``false``. Statement pooling is not compatible with PostgREST.
89 |
90 | Also set :ref:`db-channel-enabled` to ``false`` since ``LISTEN`` is not compatible with transaction pooling. Although it should not give any errors if left enabled.
91 |
92 | .. note::
93 |
94 | It’s not recommended to use an external connection pooler. `Our benchmarks `_ indicate it provides much lower performance than PostgREST built-in pool.
95 |
--------------------------------------------------------------------------------
/docs/references/health_check.rst:
--------------------------------------------------------------------------------
1 | .. _health_check:
2 |
3 | Health Check
4 | ############
5 |
6 | You can enable a health check to verify if PostgREST is available for client requests. Also to check the status of its internal state.
7 |
8 | To do this, set the configuration variable :ref:`admin-server-port` to the port number of your preference. Two endpoints ``live`` and ``ready`` will then be available.
9 |
10 | The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``503`` otherwise.
11 |
12 | The ``ready`` endpoint also checks the state of both the Database Connection and the :ref:`schema_cache`. A request will return ``200 OK`` if it is ready or ``503`` if not.
13 |
14 | For instance, to verify if PostgREST is running at ``localhost:3000`` while the ``admin-server-port`` is set to ``3001``:
15 |
16 | .. code-block:: bash
17 |
18 | curl -I "http://localhost:3001/live"
19 |
20 | .. code-block:: http
21 |
22 | HTTP/1.1 200 OK
23 |
24 | If you have a machine with multiple network interfaces and multiple PostgREST instances in the same port, you need to specify a unique :ref:`hostname ` in the configuration of each PostgREST instance for the health check to work correctly. Don't use the special values(``!4``, ``*``, etc) in this case because the health check could report a false positive.
25 |
--------------------------------------------------------------------------------
/docs/references/observability.rst:
--------------------------------------------------------------------------------
1 | .. _observability:
2 |
3 | Observability
4 | #############
5 |
6 | .. _pgrst_logging:
7 |
8 | Logging
9 | -------
10 |
11 | PostgREST logs basic request information to ``stdout``, including the authenticated user if available, the requesting IP address and user agent, the URL requested, and HTTP response status.
12 |
13 | .. code::
14 |
15 | 127.0.0.1 - user [26/Jul/2021:01:56:38 -0500] "GET /clients HTTP/1.1" 200 - "" "curl/7.64.0"
16 | 127.0.0.1 - anonymous [26/Jul/2021:01:56:48 -0500] "GET /unexistent HTTP/1.1" 404 - "" "curl/7.64.0"
17 |
18 | For diagnostic information about the server itself, PostgREST logs to ``stderr``.
19 |
20 | .. code::
21 |
22 | 12/Jun/2021:17:47:39 -0500: Starting PostgREST 11.1.0...
23 | 12/Jun/2021:17:47:39 -0500: Attempting to connect to the database...
24 | 12/Jun/2021:17:47:39 -0500: Listening on port 3000
25 | 12/Jun/2021:17:47:39 -0500: Connection successful
26 | 12/Jun/2021:17:47:39 -0500: Config re-loaded
27 | 12/Jun/2021:17:47:40 -0500: Schema cache loaded
28 |
29 | .. note::
30 |
31 | When running it in an SSH session you must detach it from stdout or it will be terminated when the session closes. The easiest technique is redirecting the output to a log file or to the syslog:
32 |
33 | .. code-block:: bash
34 |
35 | ssh foo@example.com \
36 | 'postgrest foo.conf /var/log/postgrest.log 2>&1 &'
37 |
38 | # another option is to pipe the output into "logger -t postgrest"
39 |
40 | Currently PostgREST doesn't log the SQL commands executed against the underlying database.
41 |
42 | Database Logs
43 | ~~~~~~~~~~~~~
44 |
45 | To find the SQL operations, you can watch the database logs. By default PostgreSQL does not keep these logs, so you'll need to make the configuration changes below.
46 |
47 | Find :code:`postgresql.conf` inside your PostgreSQL data directory (to find that, issue the command :code:`show data_directory;`). Either find the settings scattered throughout the file and change them to the following values, or append this block of code to the end of the configuration file.
48 |
49 | .. code:: sql
50 |
51 | # send logs where the collector can access them
52 | log_destination = "stderr"
53 |
54 | # collect stderr output to log files
55 | logging_collector = on
56 |
57 | # save logs in pg_log/ under the pg data directory
58 | log_directory = "pg_log"
59 |
60 | # (optional) new log file per day
61 | log_filename = "postgresql-%Y-%m-%d.log"
62 |
63 | # log every kind of SQL statement
64 | log_statement = "all"
65 |
66 | Restart the database and watch the log file in real-time to understand how HTTP requests are being translated into SQL commands.
67 |
68 | .. note::
69 |
70 | On Docker you can enable the logs by using a custom ``init.sh``:
71 |
72 | .. code:: bash
73 |
74 | #!/bin/sh
75 | echo "log_statement = 'all'" >> /var/lib/postgresql/data/postgresql.conf
76 |
77 | After that you can start the container and check the logs with ``docker logs``.
78 |
79 | .. code:: bash
80 |
81 | docker run -v "$(pwd)/init.sh":"/docker-entrypoint-initdb.d/init.sh" -d postgres
82 | docker logs -f
83 |
84 | Server Version
85 | --------------
86 |
87 | When debugging a problem it's important to verify the running PostgREST version. There are three ways to do this:
88 |
89 | - Look for the :code:`Server` HTTP response header that is returned on every request.
90 |
91 | .. code::
92 |
93 | HEAD /users HTTP/1.1
94 |
95 | Server: postgrest/11.0.1
96 |
97 | - Query ``application_name`` on `pg_stat_activity `_.
98 |
99 | .. code-block:: psql
100 |
101 | select distinct application_name
102 | from pg_stat_activity
103 | where application_name ilike '%postgrest%';
104 |
105 | application_name
106 | ------------------------------
107 | PostgREST 11.1.0
108 |
109 | .. important::
110 |
111 | - The server sets the `fallback_application_name `_ to the connection URI for this query to work. To override the value set ``application_name`` on the connection string.
112 | - The version will only be set if it's a valid URI (`RFC 3986 `_). This means any special characters must be urlencoded.
113 | - The version will not be set if the connection string is in `keyword/value format `_.
114 |
115 | - The ``stderr`` logs also contain the version, as noted on :ref:`pgrst_logging`.
116 |
117 | .. _trace_header:
118 |
119 | Trace Header
120 | ------------
121 |
122 | You can enable tracing HTTP requests by setting :ref:`server-trace-header`. Specify the set header in the request, and the server will include it in the response.
123 |
124 | .. code:: bash
125 |
126 | server-trace-header = "X-Request-Id"
127 |
128 | .. code-block:: bash
129 |
130 | curl "http://localhost:3000/users" \
131 | -H "X-Request-Id: 123"
132 |
133 | .. code::
134 |
135 | HTTP/1.1 200 OK
136 | X-Request-Id: 123
137 |
138 | .. _server-timing_header:
139 |
140 | Server-Timing Header
141 | --------------------
142 |
143 | You can enable the `Server-Timing `_ header by setting :ref:`server-timing-enabled` on.
144 | This header communicates metrics of the different phases in the request-response cycle.
145 |
146 | .. code-block:: bash
147 |
148 | curl "http://localhost:3000/users" -i
149 |
150 | .. code::
151 |
152 | HTTP/1.1 200 OK
153 |
154 | Server-Timing: jwt;dur=14.9, parse;dur=71.1, plan;dur=109.0, transaction;dur=353.2, response;dur=4.4
155 |
156 | - All the durations (``dur``) are in milliseconds.
157 | - The ``jwt`` stage is when :ref:`jwt_impersonation` is done. This duration can be lowered with :ref:`jwt_caching`.
158 | - On the ``parse`` stage, the :ref:`url_grammar` is parsed.
159 | - On the ``plan`` stage, the :ref:`schema_cache` is used to generate the :ref:`main_query` of the transaction.
160 | - The ``transaction`` stage corresponds to the database transaction. See :ref:`transactions`.
161 | - The ``response`` stage is where the response status and headers are computed.
162 |
163 | .. note::
164 |
165 | We're working on lowering the duration of the ``parse`` and ``plan`` stages on https://github.com/PostgREST/postgrest/issues/2816.
166 |
167 | .. _explain_plan:
168 |
169 | Execution plan
170 | --------------
171 |
172 | You can get the `EXPLAIN execution plan `_ of a request by adding the ``Accept: application/vnd.pgrst.plan`` header.
173 | This is enabled by :ref:`db-plan-enabled` (false by default).
174 |
175 | .. code-block:: bash
176 |
177 | curl "http://localhost:3000/users?select=name&order=id" \
178 | -H "Accept: application/vnd.pgrst.plan"
179 |
180 | .. code-block:: psql
181 |
182 | Aggregate (cost=73.65..73.68 rows=1 width=112)
183 | -> Index Scan using users_pkey on users (cost=0.15..60.90 rows=850 width=36)
184 |
185 | The output of the plan is generated in ``text`` format by default but you can change it to JSON by using the ``+json`` suffix.
186 |
187 | .. code-block:: bash
188 |
189 | curl "http://localhost:3000/users?select=name&order=id" \
190 | -H "Accept: application/vnd.pgrst.plan+json"
191 |
192 | .. code-block:: json
193 |
194 | [
195 | {
196 | "Plan": {
197 | "Node Type": "Aggregate",
198 | "Strategy": "Plain",
199 | "Partial Mode": "Simple",
200 | "Parallel Aware": false,
201 | "Async Capable": false,
202 | "Startup Cost": 73.65,
203 | "Total Cost": 73.68,
204 | "Plan Rows": 1,
205 | "Plan Width": 112,
206 | "Plans": [
207 | {
208 | "Node Type": "Index Scan",
209 | "Parent Relationship": "Outer",
210 | "Parallel Aware": false,
211 | "Async Capable": false,
212 | "Scan Direction": "Forward",
213 | "Index Name": "users_pkey",
214 | "Relation Name": "users",
215 | "Alias": "users",
216 | "Startup Cost": 0.15,
217 | "Total Cost": 60.90,
218 | "Plan Rows": 850,
219 | "Plan Width": 36
220 | }
221 | ]
222 | }
223 | }
224 | ]
225 |
226 | By default the plan is assumed to generate the JSON representation of a resource(``application/json``), but you can obtain the plan for the :ref:`different representations that PostgREST supports ` by adding them to the ``for`` parameter. For instance, to obtain the plan for a ``text/xml``, you would use ``Accept: application/vnd.pgrst.plan; for="text/xml``.
227 |
228 | The other available parameters are ``analyze``, ``verbose``, ``settings``, ``buffers`` and ``wal``, which correspond to the `EXPLAIN command options `_. To use the ``analyze`` and ``wal`` parameters for example, you would add them like ``Accept: application/vnd.pgrst.plan; options=analyze|wal``.
229 |
230 | Note that akin to the EXPLAIN command, the changes will be committed when using the ``analyze`` option. To avoid this, you can use the :ref:`db-tx-end` and the ``Prefer: tx=rollback`` header.
231 |
232 | Securing the Execution Plan
233 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
234 |
235 | It's recommended to only activate :ref:`db-plan-enabled` on testing environments since it reveals internal database details.
236 | However, if you choose to use it in production you can add a :ref:`db-pre-request` to filter the requests that can use this feature.
237 |
238 | For example, to only allow requests from an IP address to get the execution plans:
239 |
240 | .. code-block:: postgresql
241 |
242 | -- Assuming a proxy(Nginx, Cloudflare, etc) passes an "X-Forwarded-For" header(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)
243 | create or replace function filter_plan_requests()
244 | returns void as $$
245 | declare
246 | headers json := current_setting('request.headers', true)::json;
247 | client_ip text := coalesce(headers->>'x-forwarded-for', '');
248 | accept text := coalesce(headers->>'accept', '');
249 | begin
250 | if accept like 'application/vnd.pgrst.plan%' and client_ip != '144.96.121.73' then
251 | raise insufficient_privilege using
252 | message = 'Not allowed to use application/vnd.pgrst.plan';
253 | end if;
254 | end; $$ language plpgsql;
255 |
256 | -- set this function on your postgrest.conf
257 | -- db-pre-request = filter_plan_requests
258 |
259 | .. raw:: html
260 |
261 |
274 |
--------------------------------------------------------------------------------
/docs/references/schema_cache.rst:
--------------------------------------------------------------------------------
1 | .. _schema_cache:
2 |
3 | Schema Cache
4 | ============
5 |
6 | Some PostgREST features need metadata from the database schema. Getting this metadata requires expensive queries. To avoid repeating this work, PostgREST uses a schema cache.
7 |
8 | +--------------------------------------------+-------------------------------------------------------------------------------+
9 | | Feature | Required Metadata |
10 | +============================================+===============================================================================+
11 | | :ref:`resource_embedding` | Foreign key constraints |
12 | +--------------------------------------------+-------------------------------------------------------------------------------+
13 | | :ref:`Stored Functions ` | Function signature (parameters, return type, volatility and |
14 | | | `overloading `_) |
15 | +--------------------------------------------+-------------------------------------------------------------------------------+
16 | | :ref:`Upserts ` | Primary keys |
17 | +--------------------------------------------+-------------------------------------------------------------------------------+
18 | | :ref:`Insertions ` | Primary keys (optional: only if the Location header is requested) |
19 | +--------------------------------------------+-------------------------------------------------------------------------------+
20 | | :ref:`OPTIONS requests ` | View INSTEAD OF TRIGGERS and primary keys |
21 | +--------------------------------------------+-------------------------------------------------------------------------------+
22 | | :ref:`open-api` | Table columns, primary keys and foreign keys |
23 | + +-------------------------------------------------------------------------------+
24 | | | View columns and INSTEAD OF TRIGGERS |
25 | + +-------------------------------------------------------------------------------+
26 | | | Function signature |
27 | +--------------------------------------------+-------------------------------------------------------------------------------+
28 |
29 | .. _stale_schema:
30 |
31 | Stale Schema Cache
32 | ------------------
33 |
34 | One operational problem that comes with a cache is that it can go stale. This can happen for PostgREST when you make changes to the metadata before mentioned. Requests that depend on the metadata will fail.
35 |
36 | You can solve this by reloading the cache manually or automatically.
37 |
38 | .. _schema_reloading:
39 |
40 | Schema Cache Reloading
41 | ----------------------
42 |
43 | To manually reload the cache without restarting the PostgREST server, send a SIGUSR1 signal to the server process.
44 |
45 | .. code:: bash
46 |
47 | killall -SIGUSR1 postgrest
48 |
49 |
50 | For docker you can do:
51 |
52 | .. code:: bash
53 |
54 | docker kill -s SIGUSR1
55 |
56 | # or in docker-compose
57 | docker-compose kill -s SIGUSR1
58 |
59 | There’s no downtime when reloading the schema cache. The reloading will happen on a background thread while serving requests.
60 |
61 | .. _schema_reloading_notify:
62 |
63 | Reloading with NOTIFY
64 | ~~~~~~~~~~~~~~~~~~~~~
65 |
66 | PostgREST also allows you to reload its schema cache through PostgreSQL `NOTIFY `_.
67 |
68 | .. code-block:: postgresql
69 |
70 | NOTIFY pgrst, 'reload schema'
71 |
72 | This is useful in environments where you can’t send the SIGUSR1 Unix Signal. Like on cloud managed containers or on Windows systems.
73 |
74 | The ``pgrst`` notification channel is enabled by default. For configuring the channel, see :ref:`db-channel` and :ref:`db-channel-enabled`.
75 |
76 | .. _auto_schema_reloading:
77 |
78 | Automatic Schema Cache Reloading
79 | --------------------------------
80 |
81 | You can do automatic schema cache reloading in a pure SQL way and forget about stale schema cache errors. For this use an `event trigger `_ and ``NOTIFY``.
82 |
83 | .. code-block:: postgresql
84 |
85 | -- Create an event trigger function
86 | CREATE OR REPLACE FUNCTION pgrst_watch() RETURNS event_trigger
87 | LANGUAGE plpgsql
88 | AS $$
89 | BEGIN
90 | NOTIFY pgrst, 'reload schema';
91 | END;
92 | $$;
93 |
94 | -- This event trigger will fire after every ddl_command_end event
95 | CREATE EVENT TRIGGER pgrst_watch
96 | ON ddl_command_end
97 | EXECUTE PROCEDURE pgrst_watch();
98 |
99 | Now, whenever the ``pgrst_watch`` trigger fires, PostgREST will auto-reload the schema cache.
100 |
101 | To disable auto reloading, drop the trigger.
102 |
103 | .. code-block:: postgresql
104 |
105 | DROP EVENT TRIGGER pgrst_watch
106 |
107 | Finer-Grained Event Trigger
108 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
109 |
110 | You can refine the previous event trigger to only react to the events relevant to the schema cache. This also prevents unnecessary
111 | reloading when creating temporary tables inside functions.
112 |
113 | .. code-block:: postgresql
114 |
115 | -- watch CREATE and ALTER
116 | CREATE OR REPLACE FUNCTION pgrst_ddl_watch() RETURNS event_trigger AS $$
117 | DECLARE
118 | cmd record;
119 | BEGIN
120 | FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands()
121 | LOOP
122 | IF cmd.command_tag IN (
123 | 'CREATE SCHEMA', 'ALTER SCHEMA'
124 | , 'CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO', 'ALTER TABLE'
125 | , 'CREATE FOREIGN TABLE', 'ALTER FOREIGN TABLE'
126 | , 'CREATE VIEW', 'ALTER VIEW'
127 | , 'CREATE MATERIALIZED VIEW', 'ALTER MATERIALIZED VIEW'
128 | , 'CREATE FUNCTION', 'ALTER FUNCTION'
129 | , 'CREATE TRIGGER'
130 | , 'CREATE TYPE', 'ALTER TYPE'
131 | , 'CREATE RULE'
132 | , 'COMMENT'
133 | )
134 | -- don't notify in case of CREATE TEMP table or other objects created on pg_temp
135 | AND cmd.schema_name is distinct from 'pg_temp'
136 | THEN
137 | NOTIFY pgrst, 'reload schema';
138 | END IF;
139 | END LOOP;
140 | END; $$ LANGUAGE plpgsql;
141 |
142 | -- watch DROP
143 | CREATE OR REPLACE FUNCTION pgrst_drop_watch() RETURNS event_trigger AS $$
144 | DECLARE
145 | obj record;
146 | BEGIN
147 | FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
148 | LOOP
149 | IF obj.object_type IN (
150 | 'schema'
151 | , 'table'
152 | , 'foreign table'
153 | , 'view'
154 | , 'materialized view'
155 | , 'function'
156 | , 'trigger'
157 | , 'type'
158 | , 'rule'
159 | )
160 | AND obj.is_temporary IS false -- no pg_temp objects
161 | THEN
162 | NOTIFY pgrst, 'reload schema';
163 | END IF;
164 | END LOOP;
165 | END; $$ LANGUAGE plpgsql;
166 |
167 | CREATE EVENT TRIGGER pgrst_ddl_watch
168 | ON ddl_command_end
169 | EXECUTE PROCEDURE pgrst_ddl_watch();
170 |
171 | CREATE EVENT TRIGGER pgrst_drop_watch
172 | ON sql_drop
173 | EXECUTE PROCEDURE pgrst_drop_watch();
174 |
--------------------------------------------------------------------------------
/docs/shared/installation.rst:
--------------------------------------------------------------------------------
1 | .. tabs::
2 |
3 | .. group-tab:: macOS
4 |
5 | You can install PostgREST from the `Homebrew official repo `_.
6 |
7 | .. code:: bash
8 |
9 | brew install postgrest
10 |
11 | .. group-tab:: FreeBSD
12 |
13 | You can install PostgREST from the `official ports `_.
14 |
15 | .. code:: bash
16 |
17 | pkg install hs-postgrest
18 |
19 | .. group-tab:: Linux
20 |
21 | .. tabs::
22 |
23 | .. tab:: Arch Linux
24 |
25 | You can install PostgREST from the `community repo `_.
26 |
27 | .. code:: bash
28 |
29 | pacman -S postgrest
30 |
31 | .. tab:: Nix
32 |
33 | You can install PostgREST from nixpkgs.
34 |
35 | .. code:: bash
36 |
37 | nix-env -i haskellPackages.postgrest
38 |
39 | .. group-tab:: Windows
40 |
41 | You can install PostgREST using `Chocolatey `_ or `Scoop `_.
42 |
43 | .. code:: bash
44 |
45 | choco install postgrest
46 | scoop install postgrest
47 |
--------------------------------------------------------------------------------
/docs/tutorials/tut0.rst:
--------------------------------------------------------------------------------
1 | .. _tut0:
2 |
3 | Tutorial 0 - Get it Running
4 | ===========================
5 |
6 | :author: `begriffs `_
7 |
8 | Welcome to PostgREST! In this pre-tutorial we're going to get things running so you can create your first simple API.
9 |
10 | PostgREST is a standalone web server which turns a PostgreSQL database into a RESTful API. It serves an API that is customized based on the structure of the underlying database.
11 |
12 | .. image:: ../_static/tuts/tut0-request-flow.png
13 |
14 | To make an API we'll simply be building a database. All the endpoints and permissions come from database objects like tables, views, roles, and stored procedures. These tutorials will cover a number of common scenarios and how to model them in the database.
15 |
16 | By the end of this tutorial you'll have a working database, PostgREST server, and a simple single-user todo list API.
17 |
18 | Step 1. Relax, we'll help
19 | -------------------------
20 |
21 | As you begin the tutorial, pop open the project `chat room `_ in another tab. There are a nice group of people active in the project and we'll help you out if you get stuck.
22 |
23 | Step 2. Install PostgreSQL
24 | --------------------------
25 |
26 | If you're already familiar with using PostgreSQL and have it installed on your system you can use the existing installation (see :ref:`pg-dependency` for minimum requirements). For this tutorial we'll describe how to use the database in Docker because database configuration is otherwise too complicated for a simple tutorial.
27 |
28 | If Docker is not installed, you can get it `here `_. Next, let's pull and start the database image:
29 |
30 | .. code-block:: bash
31 |
32 | sudo docker run --name tutorial -p 5433:5432 \
33 | -e POSTGRES_PASSWORD=mysecretpassword \
34 | -d postgres
35 |
36 | This will run the Docker instance as a daemon and expose port 5433 to the host system so that it looks like an ordinary PostgreSQL server to the rest of the system.
37 |
38 | Step 3. Install PostgREST
39 | -------------------------
40 |
41 | Using a Package Manager
42 | ~~~~~~~~~~~~~~~~~~~~~~~
43 |
44 | You can use your OS package manager to install PostgREST.
45 |
46 | .. include:: ../shared/installation.rst
47 |
48 | Then, try running it with:
49 |
50 | .. code-block:: bash
51 |
52 | postgrest -h
53 |
54 | It should print the help page with its version and the available options.
55 |
56 | Downloading a Pre-Built Binary
57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58 |
59 | PostgREST is also distributed as a single binary, with versions compiled for major distributions of macOS, Windows, Linux and FreeBSD. Visit the `latest release `_ for a list of downloads. In the event that your platform is not among those already pre-built, see :ref:`build_source` for instructions how to build it yourself. Also let us know to add your platform in the next release.
60 |
61 | The pre-built binaries for download are :code:`.tar.xz` compressed files (except Windows which is a zip file). To extract the binary, go into the terminal and run
62 |
63 | .. code-block:: bash
64 |
65 | # download from https://github.com/PostgREST/postgrest/releases/latest
66 |
67 | tar xJf postgrest--.tar.xz
68 |
69 | The result will be a file named simply :code:`postgrest` (or :code:`postgrest.exe` on Windows). At this point try running it with
70 |
71 | .. code-block:: bash
72 |
73 | ./postgrest -h
74 |
75 | If everything is working correctly it will print out its version and the available options. You can continue to run this binary from where you downloaded it, or copy it to a system directory like :code:`/usr/local/bin` on Linux so that you will be able to run it from any directory.
76 |
77 | .. note::
78 |
79 | PostgREST requires libpq, the PostgreSQL C library, to be installed on your system. Without the library you'll get an error like "error while loading shared libraries: libpq.so.5." Here's how to fix it:
80 |
81 | .. raw:: html
82 |
83 |
84 |
85 | Ubuntu or Debian
86 |
87 |
sudo apt-get install libpq-dev
88 |
89 |
90 |
91 | Fedora, CentOS, or Red Hat
92 |
93 |
sudo yum install postgresql-libs
94 |
95 |
96 |
97 | macOS
98 |
99 |
brew install postgresql
100 |
101 |
102 |
103 | Windows
104 |
All of the DLL files that are required to run PostgREST are available in the windows installation of PostgreSQL server.
105 | Once installed they are found in the BIN folder, e.g: C:\Program Files\PostgreSQL\10\bin. Add this directory to your PATH
106 | variable. Run the following from an administrative command prompt (adjusting the actual BIN path as necessary of course)
107 |
111 |
112 | Step 4. Create Database for API
113 | -------------------------------
114 |
115 | Connect to the SQL console (psql) inside the container. To do so, run this from your command line:
116 |
117 | .. code-block:: bash
118 |
119 | sudo docker exec -it tutorial psql -U postgres
120 |
121 | You should see the psql command prompt:
122 |
123 | ::
124 |
125 | psql (9.6.3)
126 | Type "help" for help.
127 |
128 | postgres=#
129 |
130 | The first thing we'll do is create a `named schema `_ for the database objects which will be exposed in the API. We can choose any name we like, so how about "api." Execute this and the other SQL statements inside the psql prompt you started.
131 |
132 | .. code-block:: postgres
133 |
134 | create schema api;
135 |
136 | Our API will have one endpoint, :code:`/todos`, which will come from a table.
137 |
138 | .. code-block:: postgres
139 |
140 | create table api.todos (
141 | id serial primary key,
142 | done boolean not null default false,
143 | task text not null,
144 | due timestamptz
145 | );
146 |
147 | insert into api.todos (task) values
148 | ('finish tutorial 0'), ('pat self on back');
149 |
150 | Next make a role to use for anonymous web requests. When a request comes in, PostgREST will switch into this role in the database to run queries.
151 |
152 | .. code-block:: postgres
153 |
154 | create role web_anon nologin;
155 |
156 | grant usage on schema api to web_anon;
157 | grant select on api.todos to web_anon;
158 |
159 | The :code:`web_anon` role has permission to access things in the :code:`api` schema, and to read rows in the :code:`todos` table.
160 |
161 | It's a good practice to create a dedicated role for connecting to the database, instead of using the highly privileged ``postgres`` role. So we'll do that, name the role ``authenticator`` and also grant it the ability to switch to the ``web_anon`` role :
162 |
163 | .. code-block:: postgres
164 |
165 | create role authenticator noinherit login password 'mysecretpassword';
166 | grant web_anon to authenticator;
167 |
168 |
169 | Now quit out of psql; it's time to start the API!
170 |
171 | .. code-block:: psql
172 |
173 | \q
174 |
175 | Step 5. Run PostgREST
176 | ---------------------
177 |
178 | PostgREST can use a configuration file to tell it how to connect to the database. Create a file :code:`tutorial.conf` with this inside:
179 |
180 | .. code-block:: ini
181 |
182 | db-uri = "postgres://authenticator:mysecretpassword@localhost:5433/postgres"
183 | db-schemas = "api"
184 | db-anon-role = "web_anon"
185 |
186 | The configuration file has other :ref:`options `, but this is all we need.
187 | If you are not using Docker, make sure that your port number is correct and replace `postgres` with the name of the database where you added the todos table.
188 |
189 | Now run the server:
190 |
191 | .. code-block:: bash
192 |
193 | # Running postgrest installed from a package manager
194 | postgrest tutorial.conf
195 |
196 | # Running postgrest binary
197 | ./postgrest tutorial.conf
198 |
199 | You should see
200 |
201 | .. code-block:: text
202 |
203 | Listening on port 3000
204 | Attempting to connect to the database...
205 | Connection successful
206 |
207 | It's now ready to serve web requests. There are many nice graphical API exploration tools you can use, but for this tutorial we'll use :code:`curl` because it's likely to be installed on your system already. Open a new terminal (leaving the one open that PostgREST is running inside). Try doing an HTTP request for the todos.
208 |
209 | .. code-block:: bash
210 |
211 | curl http://localhost:3000/todos
212 |
213 | The API replies:
214 |
215 | .. code-block:: json
216 |
217 | [
218 | {
219 | "id": 1,
220 | "done": false,
221 | "task": "finish tutorial 0",
222 | "due": null
223 | },
224 | {
225 | "id": 2,
226 | "done": false,
227 | "task": "pat self on back",
228 | "due": null
229 | }
230 | ]
231 |
232 | With the current role permissions, anonymous requests have read-only access to the :code:`todos` table. If we try to add a new todo we are not able.
233 |
234 | .. code-block:: bash
235 |
236 | curl http://localhost:3000/todos -X POST \
237 | -H "Content-Type: application/json" \
238 | -d '{"task": "do bad thing"}'
239 |
240 | Response is 401 Unauthorized:
241 |
242 | .. code-block:: json
243 |
244 | {
245 | "hint": null,
246 | "details": null,
247 | "code": "42501",
248 | "message": "permission denied for table todos"
249 | }
250 |
251 | There we have it, a basic API on top of the database! In the next tutorials we will see how to extend the example with more sophisticated user access controls, and more tables and queries.
252 |
253 | Now that you have PostgREST running, try the next tutorial, :ref:`tut1`
254 |
--------------------------------------------------------------------------------
/docs/tutorials/tut1.rst:
--------------------------------------------------------------------------------
1 | .. _tut1:
2 |
3 | Tutorial 1 - The Golden Key
4 | ===========================
5 |
6 | :author: `begriffs `_
7 |
8 | In :ref:`tut0` we created a read-only API with a single endpoint to list todos. There are many directions we can go to make this API more interesting, but one good place to start would be allowing some users to change data in addition to reading it.
9 |
10 | Step 1. Add a Trusted User
11 | --------------------------
12 |
13 | The previous tutorial created a :code:`web_anon` role in the database with which to execute anonymous web requests. Let's make a role called :code:`todo_user` for users who authenticate with the API. This role will have the authority to do anything to the todo list.
14 |
15 | .. code-block:: postgres
16 |
17 | -- run this in psql using the database created
18 | -- in the previous tutorial
19 |
20 | create role todo_user nologin;
21 | grant todo_user to authenticator;
22 |
23 | grant usage on schema api to todo_user;
24 | grant all on api.todos to todo_user;
25 | grant usage, select on sequence api.todos_id_seq to todo_user;
26 |
27 | Step 2. Make a Secret
28 | ---------------------
29 |
30 | Clients authenticate with the API using JSON Web Tokens. These are JSON objects which are cryptographically signed using a secret known to only us and the server. Because clients do not know this secret, they cannot tamper with the contents of their tokens. PostgREST will detect counterfeit tokens and will reject them.
31 |
32 | Let's create a secret and provide it to PostgREST. Think of a nice long one, or use a tool to generate it. **Your secret must be at least 32 characters long.**
33 |
34 | .. note::
35 |
36 | Unix tools can generate a nice secret for you:
37 |
38 | .. code-block:: bash
39 |
40 | # Allow "tr" to process non-utf8 byte sequences
41 | export LC_CTYPE=C
42 |
43 | # Read random bytes keeping only alphanumerics and add the secret to the configuration file
44 | echo "jwt-secret = \"$(< /dev/urandom tr -dc A-Za-z0-9 | head -c32)\"" >> tutorial.conf
45 |
46 |
47 | Check that the :code:`tutorial.conf` (created in the previous tutorial) has the secret set in :code:`jwt-secret`:
48 |
49 | .. code-block:: bash
50 |
51 | # THE SECRET MUST BE AT LEAST 32 CHARS LONG
52 | cat tutorial.conf
53 |
54 | If the PostgREST server is still running from the previous tutorial, restart it to load the updated configuration file.
55 |
56 | Step 3. Sign a Token
57 | --------------------
58 |
59 | Ordinarily your own code in the database or in another server will create and sign authentication tokens, but for this tutorial we will make one "by hand." Go to `jwt.io `_ and fill in the fields like this:
60 |
61 | .. figure:: ../_static/tuts/tut1-jwt-io.png
62 | :alt: jwt.io interface
63 |
64 | How to create a token at https://jwt.io
65 |
66 | **Remember to fill in the secret you generated rather than the word "secret".** After you have filled in the secret and payload, the encoded data on the left will update. Copy the encoded token.
67 |
68 | .. note::
69 |
70 | While the token may look well obscured, it's easy to reverse engineer the payload. The token is merely signed, not encrypted, so don't put things inside that you don't want a determined client to see.
71 |
72 | Step 4. Make a Request
73 | ----------------------
74 |
75 | Back in the terminal, let's use :code:`curl` to add a todo. The request will include an HTTP header containing the authentication token.
76 |
77 | .. code-block:: bash
78 |
79 | export TOKEN=""
80 |
81 | curl http://localhost:3000/todos -X POST \
82 | -H "Authorization: Bearer $TOKEN" \
83 | -H "Content-Type: application/json" \
84 | -d '{"task": "learn how to auth"}'
85 |
86 | And now we have completed all three items in our todo list, so let's set :code:`done` to true for them all with a :code:`PATCH` request.
87 |
88 | .. code-block:: bash
89 |
90 | curl http://localhost:3000/todos -X PATCH \
91 | -H "Authorization: Bearer $TOKEN" \
92 | -H "Content-Type: application/json" \
93 | -d '{"done": true}'
94 |
95 | A request for the todos shows three of them, and all completed.
96 |
97 | .. code-block:: bash
98 |
99 | curl http://localhost:3000/todos
100 |
101 | .. code-block:: json
102 |
103 | [
104 | {
105 | "id": 1,
106 | "done": true,
107 | "task": "finish tutorial 0",
108 | "due": null
109 | },
110 | {
111 | "id": 2,
112 | "done": true,
113 | "task": "pat self on back",
114 | "due": null
115 | },
116 | {
117 | "id": 3,
118 | "done": true,
119 | "task": "learn how to auth",
120 | "due": null
121 | }
122 | ]
123 |
124 | Step 5. Add Expiration
125 | ----------------------
126 |
127 | Currently our authentication token is valid for all eternity. The server, as long as it continues using the same JWT secret, will honor the token.
128 |
129 | It's better policy to include an expiration timestamp for tokens using the :code:`exp` claim. This is one of two JWT claims that PostgREST treats specially.
130 |
131 | +--------------+----------------------------------------------------------------+
132 | | Claim | Interpretation |
133 | +==============+================================================================+
134 | | :code:`role` | The database role under which to execute SQL for API request |
135 | +--------------+----------------------------------------------------------------+
136 | | :code:`exp` | Expiration timestamp for token, expressed in "Unix epoch time" |
137 | +--------------+----------------------------------------------------------------+
138 |
139 | .. note::
140 |
141 | Epoch time is defined as the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), January 1st 1970, minus the number of leap seconds that have taken place since then.
142 |
143 | To observe expiration in action, we'll add an :code:`exp` claim of five minutes in the future to our previous token. First find the epoch value of five minutes from now. In psql run this:
144 |
145 | .. code-block:: postgres
146 |
147 | select extract(epoch from now() + '5 minutes'::interval) :: integer;
148 |
149 | Go back to jwt.io and change the payload to
150 |
151 | .. code-block:: json
152 |
153 | {
154 | "role": "todo_user",
155 | "exp": 123456789
156 | }
157 |
158 | **NOTE**: Don't forget to change the dummy epoch value :code:`123456789` in the snippet above to the epoch value returned by the psql command.
159 |
160 | Copy the updated token as before, and save it as a new environment variable.
161 |
162 | .. code-block:: bash
163 |
164 | export NEW_TOKEN=""
165 |
166 | Try issuing this request in curl before and after the expiration time:
167 |
168 | .. code-block:: bash
169 |
170 | curl http://localhost:3000/todos \
171 | -H "Authorization: Bearer $NEW_TOKEN"
172 |
173 | After expiration, the API returns HTTP 401 Unauthorized:
174 |
175 | .. code-block:: json
176 |
177 | {
178 | "hint": null,
179 | "details": null,
180 | "code": "PGRST301",
181 | "message": "JWT expired"
182 | }
183 |
184 | Bonus Topic: Immediate Revocation
185 | ---------------------------------
186 |
187 | Even with token expiration there are times when you may want to immediately revoke access for a specific token. For instance, suppose you learn that a disgruntled employee is up to no good and his token is still valid.
188 |
189 | To revoke a specific token we need a way to tell it apart from others. Let's add a custom :code:`email` claim that matches the email of the client issued the token.
190 |
191 | Go ahead and make a new token with the payload
192 |
193 | .. code-block:: json
194 |
195 | {
196 | "role": "todo_user",
197 | "email": "disgruntled@mycompany.com"
198 | }
199 |
200 | Save it to an environment variable:
201 |
202 | .. code-block:: bash
203 |
204 | export WAYWARD_TOKEN=""
205 |
206 | PostgREST allows us to specify a stored procedure to run during attempted authentication. The function can do whatever it likes, including raising an exception to terminate the request.
207 |
208 | First make a new schema and add the function:
209 |
210 | .. code-block:: plpgsql
211 |
212 | create schema auth;
213 | grant usage on schema auth to web_anon, todo_user;
214 |
215 | create or replace function auth.check_token() returns void
216 | language plpgsql
217 | as $$
218 | begin
219 | if current_setting('request.jwt.claims', true)::json->>'email' =
220 | 'disgruntled@mycompany.com' then
221 | raise insufficient_privilege
222 | using hint = 'Nope, we are on to you';
223 | end if;
224 | end
225 | $$;
226 |
227 | Next update :code:`tutorial.conf` and specify the new function:
228 |
229 | .. code-block:: ini
230 |
231 | # add this line to tutorial.conf
232 |
233 | db-pre-request = "auth.check_token"
234 |
235 | Restart PostgREST for the change to take effect. Next try making a request with our original token and then with the revoked one.
236 |
237 | .. code-block:: bash
238 |
239 | # this request still works
240 |
241 | curl http://localhost:3000/todos -X PATCH \
242 | -H "Authorization: Bearer $TOKEN" \
243 | -H "Content-Type: application/json" \
244 | -d '{"done": true}'
245 |
246 | # this one is rejected
247 |
248 | curl http://localhost:3000/todos -X PATCH \
249 | -H "Authorization: Bearer $WAYWARD_TOKEN" \
250 | -H "Content-Type: application/json" \
251 | -d '{"task": "AAAHHHH!", "done": false}'
252 |
253 | The server responds with 403 Forbidden:
254 |
255 | .. code-block:: json
256 |
257 | {
258 | "hint": "Nope, we are on to you",
259 | "details": null,
260 | "code": "42501",
261 | "message": "insufficient_privilege"
262 | }
263 |
--------------------------------------------------------------------------------
/livereload_docs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import sys
3 | from livereload import Server, shell
4 | from subprocess import call
5 |
6 | if len(sys.argv) == 1:
7 | locale = 'default'
8 | build = './build.sh'
9 | else:
10 | locale = sys.argv[1]
11 | build = f'./build.sh {locale}'
12 |
13 | call(build, shell=True)
14 |
15 | server = Server()
16 | server.watch('docs/**/*.rst', shell(build))
17 | server.watch(f'locales/{locale}/LC_MESSAGES/*.po', shell(build))
18 | server.serve(root=f'_build/html/{locale}')
19 |
--------------------------------------------------------------------------------
/postgrest.dict:
--------------------------------------------------------------------------------
1 | personal_ws-1.1 en 0 utf-8
2 | api
3 | API's
4 | APISIX
5 | Archlinux
6 | aud
7 | Auth
8 | auth
9 | authenticator
10 | backoff
11 | booleans
12 | BOM
13 | Bytea
14 | Cardano
15 | casted
16 | cd
17 | centric
18 | coercible
19 | conf
20 | Cloudflare
21 | config
22 | cors
23 | CORS
24 | cryptographically
25 | CSV
26 | durations
27 | DDL
28 | DOM
29 | DevOps
30 | dockerize
31 | eq
32 | ETH
33 | Ethereum
34 | EveryLayout
35 | filename
36 | FreeBSD
37 | fts
38 | GeoJSON
39 | GHC
40 | Github
41 | Google
42 | grantor
43 | GraphQL
44 | Greenplum
45 | gte
46 | GUC
47 | Haskell
48 | HMAC
49 | htmx
50 | Htmx
51 | Homebrew
52 | hstore
53 | HTTP
54 | HTTPS
55 | HV
56 | Inlining
57 | inlined
58 | Integrations
59 | idletime
60 | IDLETIME
61 | ilike
62 | imatch
63 | io
64 | IP
65 | isdistinct
66 | JS
67 | js
68 | JSON
69 | JWK
70 | JWT
71 | jwt
72 | Kubernetes
73 | localhost
74 | login
75 | lookups
76 | Logins
77 | LIBPQ
78 | logins
79 | lon
80 | lt
81 | lte
82 | macOS
83 | misprediction
84 | multi
85 | namespace
86 | namespaced
87 | Nanos
88 | neq
89 | nginx
90 | nixpkgs
91 | npm
92 | nxl
93 | nxr
94 | OAuth
95 | Observability
96 | OpenAPI
97 | openapi
98 | ORM
99 | ov
100 | passphrase
101 | PBKDF
102 | PgBouncer
103 | pgcrypto
104 | pgjwt
105 | pgrst
106 | pgrstX
107 | PGRSTX
108 | pgSQL
109 | authid
110 | phfts
111 | phraseto
112 | plainto
113 | plfts
114 | poolers
115 | PostGIS
116 | PostgreSQL
117 | PostgreSQL's
118 | PostgREST
119 | postgres
120 | postgrest
121 | PostgREST's
122 | pre
123 | preflight
124 | plpgsql
125 | psql
126 | RabbitMQ
127 | RDS
128 | reallyreallyreallyreallyverysafe
129 | Redux
130 | refactor
131 | reloadable
132 | Reloadable
133 | requester's
134 | RESTful
135 | RLS
136 | RPC
137 | RSA
138 | safeupdate
139 | savepoint
140 | schemas
141 | schema's
142 | SHA
143 | signup
144 | SIGUSR
145 | sl
146 | spreaded
147 | Spreaded
148 | SQL
149 | sql
150 | sr
151 | SSL
152 | stateful
153 | stdout
154 | supervisees
155 | SvelteKit
156 | syslog
157 | systemd
158 | todo
159 | todos
160 | tos
161 | tsquery
162 | tx
163 | TypeScript
164 | UI
165 | ui
166 | unicode
167 | unikernel
168 | unix
169 | updatable
170 | unfulfillable
171 | Untyped
172 | UPSERT
173 | Upsert
174 | upsert
175 | uri
176 | url
177 | urlencoded
178 | urls
179 | variadic
180 | verifier
181 | versioning
182 | Vondra
183 | Vue
184 | webhooks
185 | websearch
186 | Websockets
187 | webuser
188 | wfts
189 | www
190 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | docutils==0.17.1
2 | sphinx-copybutton
3 | sphinx-intl
4 | sphinx-rtd-theme>=0.5.1
5 | sphinx-tabs>=3.2.0
6 | sphinx>=5.0.2
7 | sphinxext-opengraph==0.9.0
8 | urllib3==2.0.7
9 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | let
2 | docs =
3 | import ./default.nix;
4 |
5 | pkgs =
6 | docs.pkgs;
7 | in
8 | pkgs.mkShell {
9 | name = "postgrest-docs";
10 |
11 | buildInputs = [
12 | docs.build
13 | docs.serve
14 | docs.spellcheck
15 | docs.dictcheck
16 | docs.linkcheck
17 | docs.check
18 | ];
19 |
20 | shellHook = ''
21 | export HISTFILE=.history
22 | '';
23 | }
24 |
--------------------------------------------------------------------------------