In code, as long as that code is JavaScript. Looking at endpoints.getPost, assuming you pass in an id, what does that result look like? "It can look like whatever query you're making" is not a good answer. Even if you're the only developer on a project, code you wrote six months ago might as well have been written by someone else.

Let's look at a GraphQL call in terms of simplicity.

await fetch({
method: 'POST',
url: 'http://server/',
body: `mutation {
deletePost(id: '542543') {
id
}
}`
});

That's it for a client call, and I know from the query that it'll return the id of the post (if any) that was deleted.

Using Postgraphile as an example, nothing to do on the GraphQL server side beyond starting it up. In your database:

CREATE TABLE post (
...
);
REVOKE ALL ON post FROM public;
GRANT SELECT INSERT UPDATE ON post TO public;
GRANT DELETE ON post TO app_admin;

Done. The issue with your approach is that different sections of code may invoke SQL that deletes data. Once a data model lives long enough, edge cases show up. By having the security model live with the data itself, you never have to worry about these edge cases. Set theory for the win! Security bubbles up from the data layer, through the app layer, to the client layer. Also means that if you have two apps talking to the same database, you are no longer required to keep those security restrictions in sync. Got one app in PHP with another in Ruby? No problem. Data security is still intact. Forgot to restrict deletes? Fix the data layer, and the security fix bubbles up. No worrying about all of the possible places that might be affected by this.

Data security at the app level is arguably the wrong level to be implementing it.

Now you may decry the complexity of a fully-formed solution, but honestly Postgraphile meets your requirements quite well. GraphQL really isn't complex on its basic level.

query {
post(id: 13245) {
id
subject
body
author {
id
name
email
}
}
}

Boom. That's the JSON that comes back—follows the structure of the query. Self-documenting. The generated CRUD endpoints cover 95% of needs. Native queries written with the database itself and exposed as functions. Migrations should be handled as either idempotent SQL DDL or repeatable sequence of files at the database level, not the app layer.

Performance-wise, I strongly doubt you'll be faster/more scalable that Postgraphile out of the box. And just because the browser isn't calling the endpoint doesn't mean that GraphQL or REST are useless. Far from it. It's absolutely not just for publicly accessible APIs. That is a false assumption on your part.

GraphQL really isn't as heavyweight as you seem to presume it is. It's just a spec. How someone implements that spec is what makes the difference. As far as request/response sequence, GraphQL is quite cheap computationally. Well documented. Only as complex as the queries you want to make. Hierarchy-aware.

You might as well argue that HTTP is too complex and inefficient; that raw socket calls are better. Why not set up an exokernel?

The reason most of us don't is that it violates the 80/20 rule. Premature optimization and all that. If 99% of the time is spent in the database doing queries and only 1% in your app, fixating on the "efficiency" or perceived "simplicity" of that 1% is a fool's errand.