Your product ends up with a backend that survives growth: services with clear boundaries, documented APIs and a data model built for the queries that actually run.

This usually starts in one of two ways. Either a new product needs its backend built and the data model and the contracts had better be right, or there is a backend that works but every change costs more than the last one.
The two cases get different treatment. On a new product the focus is laying foundations that survive growth; on one already running, removing what slows the team down without stopping delivery.
Contracts are agreed with you before implementation. Every endpoint has its schema, its error codes and its version, so your client team can move in parallel against that contract. When several teams consume the API, contract tests go in, so a change does not silently break anyone.
The schema is the most expensive thing to change later, so it gets time up front: keys, relations, what gets normalised and what does not, and which indexes the queries that will actually run need. Migrations are written so they can ship without stopping the service.
Anything that does not have to answer within the request goes to a queue: emails, webhooks, reports, syncs. With retries, idempotency and a dead letter queue somebody actually looks at, because a queue without observability just hides errors.
Many backends need an internal console or an admin panel to operate them. Those get built with React, Next.js or Angular, to the same standard: real loading and error states, and no screens that only work with perfect data.
What has to answer fast, and what gets done afterwards, outside the request.
Client
Web, mobile or a third-party integration. They all come in through the same door.
Edge
Authentication, tenant permissions and rate limiting before anything is touched.
Service
Business rules, kept apart from the framework and with their own tests.
PostgreSQL
One transaction per operation, so no write is left half done.
What is not resolved inside the request
This is the decision that costs the most to change later. It depends on how many customers there are and how much isolation the contract asks for.
tenant_id column
Works with many small customers and a team that can keep the discipline.
Schema per tenant
A good middle ground when there are dozens of customers and one of them asks for separate data.
Database per tenant
Justified with a few large customers or with compliance requirements.
Yes, and that is the usual case. The work goes in as increments that ship on their own: one part of the system gets scoped, covered with tests and then changed while delivery keeps running. Nobody has to freeze the product for weeks for this to move.
They keep owning the code. Contract and data model decisions are agreed with them, and what stays behind is the test suite and the operations docs, so they can keep going without depending on anyone external.
The one your team can maintain. Python with Django or FastAPI, Ruby on Rails and Node with NestJS or Express cover much the same ground; the choice comes from what the team can operate and what is already running, not from this year fashion.
From the data model and the boundaries between services, which are the expensive things to change later. Queries get reviewed against the expected volume, not against the development database, and anything that does not have to answer inside the request goes to a queue from the start.