In an ideal world you'd have one source of truth for what the shape of a User could be (which may well be a discriminated union of User and AnonymousUser or similar).
Without fullstack TS this could look something like: (for a Python backend) Pydantic models+union for the various shapes of `User`, and then OpenAPI/GraphQL schema generation+codegen for the TS client.
The problem with this is that your One True User shape tends to have a bunch of optional properties on it. e.g., in the user profile you fetch Post[], but in the user directory you don't, and so on with other joined properties. If every endpoint returns the One True User, then you end up needing to write conditional logic to guard against (say) `.posts` when you fetch users in the profile, even though you know that `.posts` exists.
In Typescript at least, if the discriminated union is set up correctly, you just need a single check of the type field. That lets TS narrow the type so it knows whether e.g. `.posts` is present or not.
I think I can do this, yes, but it becomes a very large amount of repetitive work.
Let's say I have api/profile (which has `.posts`) and api/user-directory (which does not). I define User as a discriminated union - one has type `user-with-posts` and one has type `user-no-posts`. OK, good so far. But now say I have a photo gallery, which returns photos on user. Now I have to add that into my discriminated union type, e.g. `user-with-posts-and-photos`, `user-with-posts-but-not-photos`, `user-with-no-posts-but-yes-photos`... and it gets worse if I also have another page with DMs...
You can use a string union to discriminate when it makes sense, but that's not the only way to discriminate and in this case you'd instead use the presence of the items themselves (essentially duck-typing with strong type guarantees)
Change the design of the API so that the posts and the photos and so on are not returned inside the user object but on the same level as it.
So {user: {...}, photos: [...]}, not {user: {..., photos: [...]}}.
Alternatively define separate schemas for each endpoint that extend the base User schema. But I'd prefer having the same structure everywhere as much as possible.
Not sure how other stacks solve this, but with GraphQL the backend defines a `User` type with a full set of fields, and the client specifies only the fields it wants to query. And with codegen you get type safety.
So on the /posts page the client asks for `{ user: { id, posts: { id, content }[] } }`, and gets a generated properly-typed function for making the query.
Without fullstack TS this could look something like: (for a Python backend) Pydantic models+union for the various shapes of `User`, and then OpenAPI/GraphQL schema generation+codegen for the TS client.