├── .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 | Cute Kittens 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 | Cute Kittens 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 |

setx /m PATH "%PATH%;C:\Program Files\PostgreSQL\10\bin"
108 |

109 |
110 |

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 | --------------------------------------------------------------------------------