PostgREST – Serve a RESTful API from any Postgres database
postgrest.org
postgrest.org
It's an insanely cool project, but I've yet to find a truly fitting use case for it. In theory, PostgREST combined with something like https://marmelab.com/react-admin/ should give you a free back-end and admin panel for most projects, but in practice, I've always found that all kinds of 'small details' won't be quite right out of the box, and that customization is really hard...
And from there you can execute your own command and add handlers or other things as you wish.
And, interesting to note that using triggers within pocketbase isn't well supported because they do a dry run insert to check against constraints and then delete it if the constraint fails.
https://github.com/pocketbase/pocketbase/discussions/650#dis...
I mention this because the OP inquired about stored procedures, somewhat related.
Still, pocketbase is so amazing.
I could be wrong but I think they use postgREST underneath.
This is what actually turned my off about it when I used it for a small hobby project: the tooling around programming in SQL is absolute crap.
Expressing business logic in stored procedures is great in theory, but in practice only the very simplest business logic will be written as stored procedures, because anything non-trivial is impossible to debug, impossible to log properly and there is never a call-stack available when the inevitable runtime error/exception occurs.
If the tooling for writing, debugging, deploying and testing stored procedures were up to the level of what Turbo Pascal was in the 80s, I'd do it in a heartbeat.
As things stand, the only way to figure out bugs in the system is to visually inspect the stored procedure source code.
If you write code in PLV8 you can mock Postgres specific global variables to debug code using Node, and testing is pretty smooth with PGTAP. The deployment story is still something every shop has to reinvent at this point though.
It's the opposite, this is a stupid idea by the sound of it, as it goes against what we've learned for decades in this industry. You yourself speculated what could be wrong with it, not exactly hard to see.
Who knows, it might be great once you actually do it, but honestly I dont think so.
Building single-page-apps with PostgREST - https://news.ycombinator.com/item?id=30132947 - Jan 2022 (84 comments)
PostgREST 9.0 - https://news.ycombinator.com/item?id=29389576 - Nov 2021 (121 comments)
PostgREST: REST API for any Postgres database - https://news.ycombinator.com/item?id=25159097 - Nov 2020 (205 comments)
PostgREST - https://news.ycombinator.com/item?id=21435195 - Nov 2019 (237 comments)
PostgREST – A fully RESTful API from any existing PostgreSQL database - https://news.ycombinator.com/item?id=13959156 - March 2017 (87 comments)
PostgREST – REST API from any PostgreSQL database - https://news.ycombinator.com/item?id=9927771 - July 2015 (204 comments)
Automatic REST API for Any Postgres Database - https://news.ycombinator.com/item?id=8831960 - Jan 2015 (68 comments)
But...
I'm leery of basing the auth on postgres' auth. It just doesn't seem up to expressing auth rules based on dynamic, app-specific business rules. (Maybe I just don't know postgres' auth mechanism well enough, though.)
Also, it's perhaps overly complicated in pointless ways. A lot of this is mapping HTTP requests to SQL... why not just accept SQL and cut out all the unnecessary mapping? E.g. (real encoding not show for clarity)
GET /theapi?sql=SELECT name, age FROM Person WHERE age >= :1&p1=21
It seems crazy to me that it defines an entire HTTP-based query language when SQL is right there to be used directly. Going to SQL directly makes the API far simpler and more powerful.
The proper way to prevent DoS is to implement some kind of resource constraints for database queries.
AFAIK for postgres every connection launches a separate database process. So in theory you can craft some kind of ulimits or cgroup limits which would restrict a process to a limited amount of RAM, CPU or IOPS. So if a given process will eat more RAM, it'll be killed, if it wants to mine bitcoins, it'll be throttled and killed by timeout eventually.
Thinking in absolutes is not very helpful. PostgREST's API is designed to prevent DoS and it is flexible enough for most of your queries and when it isn't you can write a custom function and PostgREST will expose it for you as '/rpc/<my_function>'.
You can see an example here: https://postgrest.org/en/stable/auth.html#roles-for-each-web...
The main downside of RLS is that complex queries may require hand-optimizing.
SELECT crypt( encode(digest(gen_random_bytes(1024), 'sha512'), 'base64'), gen_salt('bf', 20) )FROM generate_series(1, 1000000)
More explanations here https://www.freecodecamp.org/news/stop-calling-postgrest-mag...
The issue with using it is more around its complexity, and difficulty verifying the constraints without building custom tooling for that. You almost need something like formal methods to model your security model.
But it's an extremely powerful security tool that if you're using postgres at all, is probably worth the effort spent learning and maintaining. And if you're going that far, you already have the skills and tools to run all your auth on it so why not.
One of my related pocket theories for the next ten years: many people keep saying AI is going to eat jobs. Probably not in engineering, at least for a while. But, we're starting to see a new generation of software companies being built on these kinds of truly new-way-of-doing-things technologies, whether that's Postgrest/RLS, functions as a service, or no-code tools like Retool. The productivity and efficiency advantage that these companies will have over the incumbents is incomprehensible.
I think a lot of legacy programmers won't adjust. Its hard to admit to yourself that, honestly, we're spending most of our days doing really freakin similar things as other engineers as other companies; and we as an industry are starting to solve these problems in more and more off the shelf ways.
Which is all to say, the three-fold theory/prediction is: "backend engineering" will experience the least growth, or even highest decline, in job openings of any software engineering specialty over the next ten to fifteen years. The effort involved in it will move left-and-right on the spectrum: higher demand for frontend engineers, and higher demand for DevOps/Cloud/etc. We will see billion dollar software companies selling software as a service with the vast majority of their "backend stack" having no code (potentially some number of serverless functions to plug gaps in no-code capabilities).
You can even expose the database server to the internet and get away with it if you configure the user access control, quotas, and limitations on query complexity.
For example, these services:
https://play.clickhouse.com/play?user=play
work this way by querying ClickHouse directly.
ClickHouse has REST API as a first-class feature.
For example, this query will not start, because the amount of rows to scan is greater than the limit: https://play.clickhouse.com/play?user=play#U0VMRUNUICogRlJPT...
This query will fail with the error: https://play.clickhouse.com/play?user=play#U0VMRUNUICogRlJPT...
Code: 160. DB::Exception: Estimated query execution time (580.1882010677873 seconds) is too long. Maximum: 60. Estimated rows to process: 9946243959: While executing MergeTreeThread. (TOO_SLOW) (version 22.13.1.294 (official build))Example 1:
SELECT ... FROM table WHERE key BETWEEN c1 AND c2
- here we can get the number of records and bytes to scan before even starting the query.Example 2:
SELECT ... FROM table WHERE key = (SELECT ...)
- here we cannot know how many records will be scanned until we will finish the subquery.Maybe some estimations can be applied... but we don't do that.
You might be interested in what we're building: Seafowl, a database designed for running analytical SQL queries straight from the user's browser, with HTTP CDN-friendly caching [0]. It's a second iteration of the Splitgraph DDN [1] which we built on top of PostgreSQL (Seafowl is much faster for this use case, since it's based on Apache DataFusion + Parquet).
The tradeoff for allowing the client to run any SQL vs a limited API is that PostgREST-style queries have a fairly predictable and low overhead, but aren't as powerful as fully-fledged SQL with aggregations, joins, window functions and CTEs, which have their uses in interactive dashboards to reduce the amount of data that has to be processed on the client.
There's also ROAPI [2] which is a read-only SQL API that you can deploy in front of a database / other data source (though in case of using databases as a data source, it's only for tables that fit in memory).
Looking at the last project I did, the app-level db-middleware basically consists of two things: (1) plumbing boilerplate; (2) enforces app-level auth rules, either by preventing unauthorized operations or adding required filters. It looks like I should probably look at RLE and can likely move anything app-specific out of the middleware... which might let it collapse to something entirely generic, and therefore entirely reusable, and quite possibly something I don't have to write myself.
Sanitize your inputs. I'll let little bobby tables link to the XKCD
I'm just suggesting to not invent another query language, especially where a rather obvious and natural one already exists. You can and should sanitize untrusted input regardless.
If you're using the postgrest language your options to stabilise the interface are minimal anyway, so why not just go with SQL and a whitelist?
I am not sure if I would do this on something serious, but it was a lot of fun to set up and saved me a lot of code by reusing the comprehensive auth system that lives with the actual data in question.
It's been a while but does postgres let you re-auth on an existing connection? I was able to do that in Oracle and that allowed the use of connection pools rather than one connection per user, which can become a bottleneck.
I wish. Unfortunately, sometimes a stateless HTTP REST API is simply a non-negotiable business requirement.
Metabase works well is such scenarios.
Maybe pick a language that has better libraries for language parsing and analysis?
It feels like you're coupling your application schema to your database schema, which is something you generally want to avoid.
Is this only for niches where you are ok with the db schema being tightly coupled? Do you use specific views to decouple the two schemas? In that scenario it seems like you might eventually get to a point where your view is more complicated than setting up a more traditional application.
A lot of the time, the access patterns of an end user talking to your backend really don't match up to the access patterns of your backend talking to your database.
Perhaps what you're asking is, "Why not just have your user interface connect directly to PostgreSQL and issue SQL statements?"
> I don't want to avoid [coupling my application schema to my database schema]
It seems like you built a layer of indirection to specifically allow the thing you said you didn’t want to do a couple posts up. (I think your indirection layer is a good idea; I’m curious what your previous post meant in light of that.)
> It seems like you built a layer of indirection to specifically allow the thing you said you didn’t want to do a couple posts up
No, because my layer of indirection is in the database in the form of views and procedures. I could be wrong, but I took "coupling my application schema to my database schema" to be something like "having your HTTP API depend on objects in the database", which it does because of the way that postgREST works. If that's the kind of coupling we're talking about, then that's the kind of coupling I would rather embrace than avoid.
at the very least have a read of https://postgrest.org/en/stable/admin.html
I assume you mean readonly SQL then... (perhaps creating views too)
I’d want to limit it to simple schemes.
But if you’re only exposing one table- it would be pretty darn fast.
Advantages:
• Get back all the time spent boilerplating and bikeshedding ad-hoc app specific REST protocols.
• Eliminates the (near) superuser privileged web servers that pose a security risk if compromised. Eliminate SQL injection, XSS, XSRF as bug classes.
• Allows smart users like business analysts to bypass the UI partially or completely and go straight to a SQL console, because end users = db users 1:1 and ACLs are understood by the RDBMS directly.
• Use UI frameworks and languages that aren't JavaScript. Use context menus, menu bars, hotkeys OS services or whatever else makes your users productive.
• Use multi-threading, files, special hardware as part of your core app architecture if you need it.
• If you can afford the server side resources: align DB transaction length with UI "transaction" length.
Obviously there are also downsides. I wouldn't write Instagram this way. Postgres doesn't scale very well to lots of connections. Oracle/MSSQL scale a lot better and have other advantages like much better blob support, but you'd have to get comfortable with the idea of building new apps on them.
You can mix and match, it doesn't have to be purist. Retain a thin, simple and rarely updated web server that just handles the requests you don't send directly to the DB e.g. for things like ElasticSearch. Or if you can (i.e. not on Supabase) write custom Postgres extensions that let you use SQL stored procedures as your RPC protocol. It has some advantages over HTTP.
Lately I've been experimenting with this design a bit. The traditional hassle has been non-web distribution to desktops. https://conveyor.hydraulic.dev/ fixes that. If you're using something like Electron or the JVM you can do a build+release cycle for Win/Mac/Linux in about 60 seconds all from your dev laptop, and you can make installed clients do a fast update check on each launch just like a web app would. There are some open questions about the best way to handle user authentication when connecting direct to a DB if you don't want passwords. The nice thing is you can e.g. bind the results of SQL query or an ORM directly into your UI toolkit. JSON, REST, custom paging code and all the other goop CRUD apps end up with just boil away.
From there people mostly decide on two philosophies: "I'll write an adapter layer so that it's easy to change my data" and "I'll take those robust, fixed facts and write my application around handling them".
Honestly, I have no idea if one of those is any better than the other. I can't even say with confidence that one will lead to problems that another won't; they look equivalent to me. The choice seems to be always made based on worldview, and it's not even one of those "fast and loose" vs. "methodical" choices. All the differences I see people pointing are false ones.
- the dev workflow on a db-as-codebase system is less familiar, less well understood, with tooling general several years behind "normal" code work.
- branching and deployments similarly are just different in ways it's hard to prepare for, leading to low confidence in the deployed system.
- testing and debugging: pgtap has different constraints than normal unit testing, debugging sql functions is tricky and awkward. again the tooling is missing or far behind.
- in most profitable applications I've seen, the DB is the single largest cost and the most likely to become a bottleneck you can't loosen by throwing money at servers. having all your logic in there won't *necessarily* make this worse but it certainly won't make it better.
DBAs have dealt with all of these things for decades and they have skills and tools and mental models for them. But devs and DBAs practice different disciplines with different goals, and not everything crosses over easily. Engineers working on a system like this from either side will end up acquiring a degree of competence even expertise in the other one. Making them desirable for other employers and difficult to replace.Overall I don't strictly prefer this approach, but it definitely has under appreciated strengths and should probably be used more. It's hard to say how it could end up if more resources were put into actually developing the tooling necessary to back it up.
Logic is a very broad term, and as long as you're talking about number crunching / machine learning, I'd agree. But most web or LOB applications have pretty simple logic. According to Michael Stonbraker[1], a typical OLTP DBMS spends only 4% of its processing time doing useful work, which includes any kind of business logic, among other things. The rest is spent on housekeeping tasks such as context switching and transaction management.
The more business logic you move out of the database, i.e. to the middle tier, the more roundtrips you need per transaction. During roundtrips, transactions can't do any meaningful work, which means more idle transactions, larger connection pool, more locking, and context switches.
In other words, for typical OLTP workloads, each transaction should ideally occur in a single roundtrip, which requires the logic to reside within the DBMS.
[1] https://blog.jooq.org/mit-prof-michael-stonebraker-the-tradi...
Here's the value prop: Your database is the "source of truth" but can be accessed in many, MANY different ways. Usually via some kind of ORM system.
This can give you a head start on building a more carefully considered REST API - where it gives you the base CRUD routes for every table in an acceptable format and you can build on top of it. Or if you're accessing your DB through some other interface for your web app but need something quick to build a new face for the service like a mobile app.
I recently ditched building a traditional REST API in favor of just using what my ORM provides to interact with my DB. Something like this will come in handy if I ever need one.
Some time ago I wrote a REST wrapper for the .NET SQL connectors which allowed me to post and query data from the database. It was more than enough for my usage and I could interact with that 'service' from anywhere in the network without bothering on installing and configuring the SQL connector on the endpoints.
But after that initial step, I wouldn't want my API dependent on the schema (directly), nor would I want my database schema dependent on the API code. As a side note, that's one reason I didn't take a liking to Django.
Honestly, I'd rather have an RPC based API in the year 2023 than a REST API. REST, I think, was a bit of a mistake in terms of a source for data that would be sent to a stateful frontend as JSON. REST makes sense for webpages, but nothing about data is inherently page-like. I've run into enough quirks dealing with RESTful APIs and the libraries that claim to handle them that I think we should be looking for a better fit.
This is an interesting statement that probably should be expanded more upon!
I agree with it, because it can be nice to be able to change how certain data is returned to any consumers of your API, for convenience or maybe some business rules. For example, you might want to aggregate data from multiple tables into a single list of JSON objects for filling out a table in some application downstream. Furthermore, you might be interested in being able to change the underlying DB schema without affecting how your API returns data, since its consumers don't necessarily care about how you name your tables or what references what internally.
At the same time, I do disagree with my own point somewhat, because you can just use a DB view for pretty much the same outcome. There's no reason why MyAppUserListViewEntity couldn't match my_app.user_list_view in your database 1:1, I'd actually argue that such a mapping for reading data would be really easy to reason about and the discoverability would be pretty good, while at the same time still letting you introduce changes as necessary.
Furthermore, there's something really nice about codegen: being able to tell some generator where your local development instance of your database is running and generating application entities with all of the mappings (for example, JPA) with a single command, or doing the opposite and creating the schema from your entities. Sadly in most cases such technologies are underutilized and for whatever reason many out there still write their ORM mappings manually for something like Hibernate (or write dynamic SQL manually, with something like myBatis).
In the end, I'm not sure. Coupling might mean issues down the road, but decoupling now might mean introducing a level of abstraction/indirection that might just be needless cruft, like the tendency that you sometimes see in Java projects, along the lines of: MyBusinessObject/Dto <--> SomeMapper <--> MyEntity <--> MyEntityDao <--> MyEntityMapper/Repository; Not saying that that's necessary OR that it's a bad approach, Java just has lots of codebases out there that end up with many abstractions, hence the example.
The first (theoretical) benefit is that it removes a lot of redundancy. Databases already offer a lot of things applications do for themselves, and typically it's a best practice to do those things in the database anyway to guard against application bugs. For instance, defining CHECK constraints is a best practice regardless of application validation. (There's a lot of disagreement over where the DB/app boundary is and how much overlap there should be.)
Second, databases can be declarative because they are managing the data itself. The presence of a constraint makes a guarantee about the data regardless of history (versions, changes, bugs, etc.). Similarly for declarative authorization (GRANT, RLS, etc.).
Third, these benefits compound when dealing with many smaller, hastily-written applications.
https://supabase.com/blog/postgrest-11-prerelease
(Disclosure: this points to the supabase blog and I work at supabase (as does Steve))
Some of the features include
- Spreading related tables - similar to a JS "..." spread operator to flatten a response
- Order by on related tables
- "Anti-Joins" - e.g. filter the rows where the related table is null.
I'm well aware the open source attitude means "go and fork it + PR it yourself if you want it so bad". Easier said than done as an outsider into a massive Haskell codebase.
Transactions (2015): https://github.com/PostgREST/postgrest/issues/286
GROUP BY/DISTINCT (2017): https://github.com/PostgREST/postgrest/issues/915
And yes, I’m aware you can use an RPC or a view to work around that, but doing so very much black boxes your application logic into SQL and should be heavily avoided for several reasons and using those things come with their own limitations as well (can’t use Supabase Realtime on RPC or view, as one example).
*when it comes to side projects and fun projects that I am the primary user
Its filtering DSL is awesome, and should probably get supported by commune: https://postgrest.org/en/stable/api.html#operators
Note that I didn't use it yet in production settings, but intent to. Auth do seem a bit complex.
For anybody wanting a quick test on Windows: https://github.com/majkinetor/postgrest-test
That being said, I think it's a little funny because it epitomizes APIs that I see people build where ACID compliance is a huge "must" for their database choice and then they expose resources which can only be independently updated in a manner that fails to expose ACID beyond the scope of individual resources. I think this is funny because it really makes you question if they ever actually needed full mutli-table transactions at all or if they could have used a different database all together.
Anyways, it's a really cool tool.
Then if I am going to write an API for writing data, just adding some for reading data doesn't seem like much effort, although I see the value proposition as frontend development getting a very consistent API without having to ask the backend to develop APIs.
Your business logic works on the event stream that comes from the WAL.
Same for transformation server side, unless I'm wrong, you are not limited to ORM like construct. You can call a stored procedures and only the result of server side computation is sent back.
Evidently, even some experts do use triggers.
"I normally limit them to at most one per operation per TRIGGER"
Great! So do I! So far so good. I guess I'm still in the good graces of some experts.
"in full ANSI/ISO SQL, we do have schema level constraints, the CREATE ASSERTION statement, but that is another topic"
Not in PostgreSQL, we don't. Evidently some experts may not be perfect experts.
https://github.com/PostgREST/postgrest/blob/main/cabal.proje...
https://github.com/PostgREST/postgrest/blob/main/default.nix
https://github.com/PostgREST/postgrest/blob/main/shell.nix
https://github.com/PostgREST/postgrest/tree/main/nix
I couldn't for the life of me figure out how to play with this. I kind of don't believe "docker build ." is worse but I might be missing something.
After I ejected out of playing with it on Mac OS, I found: https://github.com/NixOS/nix/issues/458#issuecomment-1019743...
Over 13 parts to remove NixOS from Mac OS, involving reboots, /etc/fstab, OS level users, daemons, etc.
https://news.ycombinator.com/item?id=33894995#33897716
https://news.ycombinator.com/item?id=33484693#33485145
https://news.ycombinator.com/item?id=33078798#33119873
https://news.ycombinator.com/item?id=30636796#30637326
https://news.ycombinator.com/item?id=24442294#24442876
Any recommendations or anecdotes today?
You can try it out here: https://pockethost.io/
Full fledge project implemented, interesting use of PostgREST
We release our new HTTP interface with our free AIoT database - JoinBase:
HN discuss: https://news.ycombinator.com/item?id=34181591
or the blog link: https://joinbase.io/blog/http-interface/
Different to PostgREST, the HTTP interface of JoinBase is integrated in the database. So, no verbose setup, one binary rules them all!
Our HTTP interface is inspired from the ClickHouse, but we provide 100x message throughput than that of ClickHouse in the HTTP interface. JoinBase's HTTP interface is so fast that you can use it to provide unlimited production-level REST services without any worry.
If someone are interesting for JoinBase, just request the free distribution here: https://joinbase.io/request/
Postgres has access control and an extension mechanism. Would the easiest be to use a real postgres server?
1) you need to separate any non-queryable data into it’s own table. So you’d need _public and _private tables for most data types
2) any type of filter query will need a JOIN, which leaves you vulnerable to malicious long running queries
3) some tables you don’t want people to run SELECT * on, otherwise they can get your entire site data
4) you need to be really careful with DELETE, UPDATE and INSERT queries to prevent malicious behavior
Maybe one way would be to write a bunch of whitelisted regex patterns for allowed queries, but you’re definitely playing with fire allowing someone to send SQL queries to your DB.
Things like personal user / marketing info, internal data / metrics, etc.
From my experience, scaling a typical site has always been bottlenecked in the database. http workers are generally stateless and easily scalable. Where it gets tricky is optimizing the database reads, caching, and invalidation.
Is the intended use case just simple CRUD of data that it not under extreme read or write loads?
It seems like you could quickly build CRUD for tables in _language of choice_ and still have the ability to optimize reads with caching later..
Today PRQL is on the frontpage, I wonder if postgrest could support it?
edit: PRQL compiles down to SQL, so it should be possible to use it for postgrest, it just adds an extra step
Now PostgREST handles all that, and only a few less trivial endpoints are handled by a custom backend (including streaming, which I'm considering replacing with postgrest-websocket [2] at some point).
During the switch to PostgREST, the encountered minor issues were those with inherited tables (had to set a bunch of computed/virtual columns [3] in order to "embed" those), and with a bug on filtering using such relations (turned out it was an already-fixed regression [4], so an update helped). Also a couple of helper stored procedures (to use via /rpc/) for updates in multiple tables at once (many-to-many relationships, to edit entities along with their relationships, using fewer requests) were added (though the old custom backend didn't have that), the security policies were set from the beginning, the frontend was rewritten (which allowed to finally switch without adding more work), so it was only left to cleanup the backend.
Not using views, since as mentioned above, database changes usually correspond to frontend changes, and the API doesn't have to be that stable yet.
Happy with it so far.
[1] https://postgrest.org/en/stable/api.html#embedding-with-top-...
[2] https://github.com/diogob/postgres-websockets
[3] https://postgrest.org/en/stable/api.html#computed-virtual-co...
We observed that people migrated off as soon as the applications went into production or were serious, as the database<->api approach did not actually give them all the flexibility they wanted.
(This was 3 years ago, the players that stayed in that market or entered later, of course learned from that and adapted. So I hear they are making their users happy. As are we as an ORM.)
It’s interesting to me that PostgREST and Hasura are written in Haskell, but PostGraphile is not. Given the complexity in parsing arbitrary GQL queries I think I assumed that Haskell was a better fit for the job, hence the mixup!