Design Philosophy
goal: Schema → API
Can we use the declarative information in a
relational db schema to mechanically generate
an HTTP API?
Tables/views → Routes
Always one level deep
WHERE clause → query params
Just simple operators like less, greater,
equality, inequality
Provide more complicated logic as views
Primary key → eq: param(s)
A primary key identifies at most a single row
They can be compound and/or natural
Routes like /foo/1 don’t always suffice
/foo?k1=eq.v1&k2=eq.v2
For instance,
if an employee belongs to a dept
Link: <http://me.com/dept?id=eq:7>; rel="dept"
Foreign keys → header links
Limit, offset → Range headers
HTTP/1.1 206 Partial Content
Accept-Ranges: items
Content-Range: 0-24/100
Range-Unit: items
RFC7233
GET /items HTTP/1.1
Range-Unit: items
Range: 0-24
SQL update → HTTP patch
PATCH /people?age=lt.12
{ "type": "child" }
Updates all the matching ones
SQL insert → HTTP post
POST /items
PUT /items?pk=eq.val
with all fields specified in body
SQL upsert → HTTP put
schema search path → API Version
Use numerical schema names in the db
Set schema search path to mask endpoints and
fall through when desired
GET /foo HTTP/1.1
Accept: application/vnd.me.com+format; version=n
DB Roles → OAuth
Web server authenticates you
then connects as your user to db
DB authorizes your (hence server) actions
Column constraints → OPTIONS
We can pipe the options output into a client
side “Faker” to mock server responses for the
client test suite.
PG stats collector → cache headers
The access and modification patterns in a table
can provide a heuristic for http caching headers
Perhaps a special view could allow overriding
etags, max-age, etc for each table via a sql
expression
EXPLAIN → HTTP 413
The server can examine the query plan and
preemptively forbid inefficient requests
Like large seq scans or nested join loops
Deployment is easy
For instance on Heroku create a buildpack that
installs the server binary
Then push new migrations and run them. The
only way to update your api server is through
migrations.
Use robust migration tool
github.com / theory / squitch
• Follows a git-inspired workflow
• Models dependencies as a graph, not a line
• TDD at the SQL level!
• No conflicts between branches with git union
merge strategy (unlike schema.rb)
Efficiency!
This can be much
faster than Rails
Efficiency! Use a fast language.
The web server itself doesn’t change so we
don’t need the convenience of a scripting
language.
Compile it once for your platform.
I’m using the Warp server on Haskell
Removing imperative cruft
Processing that happens in Ruby models is
glacially slow compared with tuned C inside
postgres.
Generate JSON inside Postgres
Dockyard measured that Postgres’ internal
JSON generation was 2X/10X faster than
ActiveRecord Serializers for small data and
160X faster for large data.
github.com/begriffs/postgrest
github.com/begriffs/postgrest-heroku
github.com/begriffs/postgrest-example

PostgREST Design Philosophy

  • 1.
  • 2.
    goal: Schema →API Can we use the declarative information in a relational db schema to mechanically generate an HTTP API?
  • 3.
  • 4.
    WHERE clause →query params Just simple operators like less, greater, equality, inequality Provide more complicated logic as views
  • 5.
    Primary key →eq: param(s) A primary key identifies at most a single row They can be compound and/or natural Routes like /foo/1 don’t always suffice /foo?k1=eq.v1&k2=eq.v2
  • 6.
    For instance, if anemployee belongs to a dept Link: <http://me.com/dept?id=eq:7>; rel="dept" Foreign keys → header links
  • 7.
    Limit, offset →Range headers HTTP/1.1 206 Partial Content Accept-Ranges: items Content-Range: 0-24/100 Range-Unit: items RFC7233 GET /items HTTP/1.1 Range-Unit: items Range: 0-24
  • 8.
    SQL update →HTTP patch PATCH /people?age=lt.12 { "type": "child" } Updates all the matching ones
  • 9.
    SQL insert →HTTP post POST /items
  • 10.
    PUT /items?pk=eq.val with allfields specified in body SQL upsert → HTTP put
  • 11.
    schema search path→ API Version Use numerical schema names in the db Set schema search path to mask endpoints and fall through when desired GET /foo HTTP/1.1 Accept: application/vnd.me.com+format; version=n
  • 12.
    DB Roles →OAuth Web server authenticates you then connects as your user to db DB authorizes your (hence server) actions
  • 13.
    Column constraints →OPTIONS We can pipe the options output into a client side “Faker” to mock server responses for the client test suite.
  • 14.
    PG stats collector→ cache headers The access and modification patterns in a table can provide a heuristic for http caching headers Perhaps a special view could allow overriding etags, max-age, etc for each table via a sql expression
  • 15.
    EXPLAIN → HTTP413 The server can examine the query plan and preemptively forbid inefficient requests Like large seq scans or nested join loops
  • 16.
    Deployment is easy Forinstance on Heroku create a buildpack that installs the server binary Then push new migrations and run them. The only way to update your api server is through migrations.
  • 17.
    Use robust migrationtool github.com / theory / squitch • Follows a git-inspired workflow • Models dependencies as a graph, not a line • TDD at the SQL level! • No conflicts between branches with git union merge strategy (unlike schema.rb)
  • 18.
    Efficiency! This can bemuch faster than Rails
  • 19.
    Efficiency! Use afast language. The web server itself doesn’t change so we don’t need the convenience of a scripting language. Compile it once for your platform.
  • 20.
    I’m using theWarp server on Haskell
  • 21.
    Removing imperative cruft Processingthat happens in Ruby models is glacially slow compared with tuned C inside postgres.
  • 22.
    Generate JSON insidePostgres Dockyard measured that Postgres’ internal JSON generation was 2X/10X faster than ActiveRecord Serializers for small data and 160X faster for large data.
  • 23.