Hacker News new | past | comments | ask | show | jobs | submit login

Because I’m otherwise uninformed to pros/cons here and don’t have the benefit of experience to fall back on, what are the alternative suggestions for best practices for these types of problems? For context, my app is a single backend dev (me), separate frontend team that tasks on as needed. Backend dev resources are unlikely to expand beyond me for the 2-year horizon.

I also have all context on vision for direction of the web app, and field all user feedback and requests. Front end team has no context on user needs, and does not keep any roadmap for the app. So it seems like the above recommended pattern could make sense in that I could drive more front end work from the backseat, so to speak.




I think the standard evolution of an API is:

1) basic CRUD API for your objects (eg DRF generated model-API in Django). Really easy to build. Client is assumed to have some magic knowledge (eg which country code style you use). Ideally as much of the smarts as possible is server-side, but early on you can compromise on this.

2a) for reads, notice you need nesting / joins, add nested fields. Maybe notice these joins are slow and make them optional; build some expanded=true or with_subobject=true type query params.

2b) for writes, notice that CRUD is getting hard to maintain as frontend can create n^2 possible states for n fields, and backend validation needs to consider all other fields for each update. Move complex state transitions to mutation/action/verb endpoints instead of PUT(any fields). Add your first “status” fields.

This gets you quite far, likely to be fine for 2 years and beyond. After this there are lots of options depending on needs, and overhead associated with more layers/abstraction;

3) use something structured like GraphQL, or just refactor the API to do away with directly updating objects. Maybe lean into Finite State Machines if you have a small number of objects with complex transitions, endpoints trigger those parameterized transitions.

Another option to consider is a server-side rendered framework with client-side hooks like Django+HTMX, Rails Hotwire, Elixir Livewire, where the frontend devs are working on styling, design, templates, and most/all the actual app logic is backend code. This gets you surprisingly far these days. If you are the one defining all the features, you’ll iterate quicker in this mode.


Thanks for the thorough comment, I appreciate your insights. I was thinking about something like htmx since it seems to be getting a lot of positive buzz lately. The only thing keeping me from going that way is that I can offload most of the frontend work to folks who understand it better.

For 2b, could you elaborate a bit? Is this like having dedicated handling for specific functionality? If I understand you, for example instead of PUT-ing some status on /myobject/{id}/, I could have something like /myobject/{id}/update_status/. And the benefit here is assuming that setting status is not a straightforward transition (maybe requires other transformations, etc), that this lets you better compartmentalize this complex logic instead of having one giga-endpoint for all PUTs?


RE delegating frontend work, it’s a valid concern. Might be worth it to see how much of the Django templating work you could have the FE developers do; you can often still split it so that they do the presentation work. (Great thing about Django/HTMX is you still work with HTML generated in template fragments). The “API” is instead the state that you push into the template context. It’s a really productive development loop.

On 2b, yep exactly as you say. Instead of updating arbitrary fields with PUT/PATCH, you would have an endpoint per action. (Or a mix of both approaches if it is more expedient).

This doesn’t make much sense for “data bag” objects with lots of potential fields to edit like a user’s profile. But say your object has a mutation that always takes one parameter to update a model with many other incidental fields, you can map that to a POST /obj/<id>/update_foo {param} which is basically an RPC mapping to a single function.

This makes most sense when you have objects that look like FSMs since in that case you might have a few transitions, each taking different args, and each dependent on a specific state as precondition. But I have used this for e.g.“users/<id>/activate” which updates the status, sets a updated_at timestamp, and cannot be undone. The alternative is something like PATCH the is_active field, and having some gnarly logic to handle bookkeeping around time stamps and preventing reverting.

Another way of thinking about it is if any combination of values is valid, it doesn’t help. But as soon as you need to handle state transitions and particularly when you have multiple, then simply updating raw fields results in extremely messy validation logic within your update endpoint.


For reads and offloading front-end work, I think the article's approach should be good for your use case. Just make sure that your backend code is cleanly written. Create proper domain objects to represent pages that you will be serving as json. Pass all dependencies into these objects to construct them.

For writes, a typical rest approach as described in the grandparent is good.

Using specific actions such as `update_status` vs. allowing any fields to be supplied is… I think both can work. But it's not easy to cleanly organize the latter. It might be important to split different groups of related fields into separate logical paths, as though they were indeed supplied via separate endpoints. In Ruby/Rails I had success doing this via modules included into the model. The advantage of letting any fields be updated in any combination is that this would allow you to build different interfaces (i.e. admin UI, user UI, using REPL console if your language supports it) with different combinations of fields, and expect them all to work. You don't have to do that if you don't anticipate this need yet, but it's also true that splitting this logic into different urls is not all that different from splitting its handling under the hood, while allowing all fields submitted at once.


Given that you're the sole backend developer with a focused vision for the app, the Server-Informed UI approach described in the article could work well for you. It simplifies the backend architecture and makes it easier to coordinate with your frontend team.

However, for long-term scalability, you might run into issues with this approach. If so, take a look at "Bounded Contexts," a technique in Domain-Driven Design that helps break down your application into manageable, scalable units.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: