Yeah, this is very cool, and I’m surprised I hadn’t heard about it. Though I think we’re finally at the point where async python web frameworks and tools are great, to the point where I’m no longer jonesing for something better like I used to, both in terms of performance and usability.
Yeah, aiohttp was where it all really began, and sanic is nice for recovering Flask users, but if you really want to see performance and momentum, check out Starlette (https://www.starlette.io) and FastAPI (https://fastapi.tiangolo.com) which is built on top of Starlette and is very clever and probably the most rapid way to build an API that I’ve ever seen. In most practical tests Starlette also outperforms aiohttp/etc. by a factor of 5, and the ASGI spec it’s built on is the actual successor to WGSI and the future of python web programming.
Things have come a very long way the last handful of years, and there are many great options. Nice to see yet another one.
For anyone interested in Starlette or general async programming in Python, I also want to point out that there's a few great packages maintained by Encode/Tom Christie (of Django Rest Framework fame).
The developer ergonomics are getting really good, I especially love encode/databases, which has full support for SQLAlchemy Core, and encode/httpx, a requests-like HTTP client.
Why do project like this spawn in hordes? I don’t ask it to criticize kore4 in any way, let me explain.
Every new framework for web does almost the same thing in slightly different way: serve http routes by assigning functions to it and inserting some middleware to check/replace something in the request. But why is it important at all? Why don’t we see numerous both-side frameworks which make http (rest, ws, etc) completely transparent?
As a long-time desktop dev I would like to use an object lib, which would take the burden of api and provide clean interfaces between client/server/db. Like this pseudocode:
# backend
@net()
@db()
class Car:
members
methods
Car.export(some_members)
Car.export(some_methods)
// front
let edit = await db.edit()
let car = edit.new(db.Car)
car.year = 2013
car.pic = await edit.upload(some_pic, progress_handler)
car.vendor = db.Vendor.find({
title:’Ford’})
...
await edit.save()
// or edit.cancel()
// which also removes pic
// eventually
let car = db.Car.find({year:2013})
car.drive_to(some_loc)
# car drives to some_loc in backend
# Your taxi app prototype
# almost done here
Instead we happily define routes in yet another way, which feels like when I was in a school writing masm 100-liners to open a file and print its contents. Am I missing something?
I know about vaadin and outsystems, but these are enterprise behemots, one paid, one too complex* and opinionated to begin with. Why not npm install dbnetlib, app.use(dbnetlib()) and go create?
* a good sign that you’ll have to jump through hoops on non-standard tasks.
Because that approach fits the problem of implementing a request/response message pattern to a tee.
> Why don’t we see numerous both-side frameworks which make http (rest, ws, etc) completely transparent?
Because you'd be making the mistake of mixing up how to handle your messages with how you parse and interpret the messaging protocol.
You should not be mixing up responsibilities which are entirely orthogonal. You either parse HTTP requests to decide how their messages should be interpreted (i.e., routing) or you act on the message that has been correctly routed (i.e., what you actually wanted to do in the first place ).
Do keep in mind that HTTP just serves as a messaging protocol, and in order to deliver a message first you need to validate it and pick where and how it should be handled. Web APIs are designed arbitrarily to depend on some features of HTTP to validate and route messages, and some of these design decisions are so common that some web framework make developers' lives easier by providing built-in modules that avoid boilerplate code (i.e., middleware).
If you're developing a web API and you are systematically having to deal with HTTP then, to put it bluntly, you are doing it wrong. That job is already done if you pick one of these frameworks, and you're simply reinventing the wheel with no good reason.
> Instead we happily define routes in yet another way, which feels like when I was in a school writing masm 100-liners to open a file and print its contents. Am I missing something?
Yes, the fact that with these frameworks you are able to focus on implementing what you're supposed to be implementing (essentially message handlers) instead of having to waste your time reinventing the wheel filling in boilerplate code just to get to your task at hand.
What are frameworks/middleware you’ve mentioned? I’m confused since if the snippets above reinvent some wheel, I would like to use that wheel instead.
The other commenter suggested looking at Phoenix/Elixir, but its getting started doc doesn’t seem to do net-transparent objects - it’s the same “hey route, hey handler” approach. My idea is that if you do e.g. a taxi app, then the only ‘routes’ you should care about are routes on the map.
> My idea is that if you do e.g. a taxi app, then the only ‘routes’ you should care about are routes on the map.
There are no routes on the map.
As a backend developer, your job is to define what are the valid routes and how to handle non-compliance but valid requests.
Take for example content negotiation. Do you honour the Accept header or do you serve content based on the request path, such as the resource extension? What should you do if you receive a request where both approaches are used but in a contradicting manner?
Now, keep in mind that API versioning is handled as content negotiation.
Now, let's add auth to the mix. Do you intend to hand-roll a few OpenId connect workflows by hand, which involve 3 or 4 HTTP requests behind exchange between 2 or 3 actors? Are you intending to hand-roll token expiration checks? Or do you prefer to use an API key or session cookies, or any other, or a mix of all of them put together?
And what about caching? Are you expecting to check and tweak cache control and etags headers each and every time you receive a request?
Or do you only want to receive a request and act upon it's request body?
If you use one of these web frameworks, all you need to do is configure your middleware to specify how you handle auth and cache and content negotiation, define routes based on the outcome of your content negotiation process, and proceed to implement your web API.
The answer to your implicit question IMO is that routing is hard abstract away, universally. For every approach to do it, there will be detractors claiming its not good enough.
So à library author gets traction by leaving the bike shed plumbing to the API user.
> Why don’t we see numerous both-side frameworks which make http (rest, ws, etc) completely transparent?
I think you want RPC. There are many solutions. Most popular are corporate-blessed. Not sure if anyone likes them. They were always kind of pain to use. But that might have been due to mismatch between actual network requests that are intrinsically asyc and can fail any time for whatever reason and function calls that are quick and always succeed (except for specific scenarios).
As more desktop programmers get familiar with async and that most async do not necessarily need to succeed maybe it's time to revisit the idea of RPC in open source setting.
I'm a bit afraid though that in current climate of every language getting 'await' keyword people come back to mindset of 'Yeah, I'll wait. Fail? What fail?'
Yeah, I omitted error handling for clarity - ofc, edit.upload and edit.save/cancel may throw from await (also invalidating the entire `edit` automagically if unhandled, thus leaving no garbage anywhere). HN comments are just not suitable to define everything in detail.
RPC is a known thing, but I find it too low-level, much like rest/post api. As at the end of the day all of our data is basically a consistent collection of linked objects, and it would be great to operate on objects in a transaction (‘edit’), not on function calls. When you’re in a network-wide transaction, time and errors are no big deal. Rollback, rinse, repeat.
My RPC right now is:
// front
try {
id = await rpc(‘save’, {obj})
}
// back
app.rpc(‘save’, async (task, p) => {
id = await do_save(p.obj) // may throw
task.ok(id)
})
It’s not very clever or ‘amazing’.
>in current climate of every language getting 'await' keyword people come back to mindset of 'Yeah, I'll wait. Fail? What fail?'
I don’t find it problematic, honestly. People will always misunderstand and misuse. It’s business, we need to build fast, not to care how good someone understands basic programming.
Phoenix (elixir) does exactly what you seem to want.
(Easily defining rest APIs from object definitions, automagically establishing sockets for extremely easy communication between backed and frontend, and even things like Phoenix live view to reduce the amount of frontend code you have to write.)
Honestly, it wasn't as great as it seemed when I experimented with it.
The only thing I really used after the first experiment was the auto sockets (which are fantastic). Everything else was basically unusable because straight db bindings are extremely rare. You mostly want to specialize it with custom authentication etc.
And there are better options if you really just want a thin wrapper over you database like postgrest or it's equivalent for graphql.
These can also be extended for custom methods so you're not forced to put everything in the frontend
Meteor (https://www.meteor.com/) invented its own protocol, over web sockets I think. It's an interesting proof of concept of how you can to away with the back-and-forth ceremony, but the framework has had some scaling issues and didn't become a huge success.
> Why don’t we see numerous both-side frameworks which make http (rest, ws, etc) completely transparent?
Fulcro[0] has this story. It's a full stack CQRS library that works by sending queries and commands over a single /api endpoint. You don't write HTTP plumbing, you write queries and mutations on your data (and any resulting change in state is reflected automatically in your view, leveraging React).
It also has great functionality to normalize and manage your front-end state, but that's extra.
"Why don’t we see numerous both-side frameworks which make http (rest, ws, etc) completely transparent?"
This was the main point of Meteor (ddp / minimongo). Ok, among with reactivity and isomorphism. I loved it, until things got complicated with react and graphql.
Yeah, aiohttp was where it all really began, and sanic is nice for recovering Flask users, but if you really want to see performance and momentum, check out Starlette (https://www.starlette.io) and FastAPI (https://fastapi.tiangolo.com) which is built on top of Starlette and is very clever and probably the most rapid way to build an API that I’ve ever seen. In most practical tests Starlette also outperforms aiohttp/etc. by a factor of 5, and the ASGI spec it’s built on is the actual successor to WGSI and the future of python web programming.
Things have come a very long way the last handful of years, and there are many great options. Nice to see yet another one.